package kvm2 import ( "context" "errors" "fmt" "github.com/digitalocean/go-libvirt" "github.com/go-logr/logr" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) // Get is spec entries 7 to 9: look the domain up by name, read the mark // back, then state and address. Only ErrNoDomain becomes NotFound; every // other failure is classified, never guessed. func (p *Provider) Get(ctx context.Context, providerID string) (*provider.Instance, error) { hostName, name, err := parseProviderID(providerID) if err != nil { return nil, provider.Wrap(provider.ErrPermanent, "get", p.name, providerID, err) } host, err := p.host(hostName) if err != nil { return nil, p.wrapErr("get", providerID, err) } log := p.logger(ctx).WithValues("host", host.Name, "name", name) be, err := p.hosts.get(ctx, host) if err != nil { logAPIError(log, "get", err) return nil, p.wrapErr("get", providerID, err) } inst, err := get(ctx, log, be, host.Name, name) if err != nil { if hasCode(err, libvirt.ErrNoDomain) { log.V(1).Info("kvm2 domain not found") } else { logAPIError(log, "get", err) } return nil, p.wrapErr("get", providerID, err) } log.V(1).Info("kvm2 domain fetched", "state", inst.State, "ip", inst.IP, "uid", inst.UID) return inst, nil } // get builds the Instance from the domain's metadata, its state and, for an // active domain, its discovered address. func get(ctx context.Context, log logr.Logger, be backend, hostName, name string) (*provider.Instance, error) { dom, err := be.DomainLookupByName(ctx, name) if err != nil { return nil, fmt.Errorf("looking up domain %s: %w", name, err) } meta, err := readMetadata(ctx, be, dom) if err != nil { return nil, err } state, _, err := be.DomainGetState(ctx, dom, 0) if err != nil { return nil, fmt.Errorf("reading state of domain %s: %w", dom.Name, err) } s := libvirt.DomainState(state) var ip string if isActive(s) { if ip, err = address(ctx, log, be, dom); err != nil { return nil, err } } return &provider.Instance{ ID: formatProviderID(hostName, dom.Name), IP: ip, State: mapState(s, ip), UID: meta.UID, CreatedAt: meta.createdAt(), }, nil } // readMetadata returns the kvm2 mark of a domain, or the zero value (not // managed, empty UID) when the domain carries none. A mark that does not // parse is Permanent, from parseInstanceMetadata. func readMetadata(ctx context.Context, be backend, dom libvirt.Domain) (instanceMetadata, error) { raw, err := be.DomainGetMetadata(ctx, dom, int32(libvirt.DomainMetadataElement), libvirt.OptString{metadataURI}, libvirt.DomainAffectCurrent) if hasCode(err, libvirt.ErrNoDomainMetadata) { return instanceMetadata{}, nil } if err != nil { return instanceMetadata{}, fmt.Errorf("reading metadata of domain %s: %w", dom.Name, err) } return parseInstanceMetadata(raw) } // addressSources is the discovery order of spec entry 7: the guest agent // answers where the host's ARP table never sees guest traffic. var addressSources = []struct { name string src libvirt.DomainInterfaceAddressesSource }{ {"agent", libvirt.DomainInterfaceAddressesSrcAgent}, {"arp", libvirt.DomainInterfaceAddressesSrcArp}, } // address returns the first usable IPv4 from the first source that yields // one, or "" for "no address yet". A source that fails is logged and // skipped; only ctx ending is an error, so a cancelled Get does not read // as Provisioning. func address(ctx context.Context, log logr.Logger, be backend, dom libvirt.Domain) (string, error) { for _, s := range addressSources { ifaces, err := be.DomainInterfaceAddresses(ctx, dom, uint32(s.src), 0) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return "", fmt.Errorf("reading addresses of domain %s: %w", dom.Name, err) } log.V(2).Info("no addresses from source", "source", s.name, "error", err.Error()) continue } if ip := firstIPv4(ifaces); ip != "" { return ip, nil } } return "", nil }