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 &copy
   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  }