diff --git a/examples/v0.13/launch_security/main.tf b/examples/v0.13/launch_security/main.tf new file mode 100644 index 000000000..5a01e65c7 --- /dev/null +++ b/examples/v0.13/launch_security/main.tf @@ -0,0 +1,44 @@ +# Configure the provider +provider "libvirt" { + uri = "qemu:///system" +} + +# SEV example +resource "libvirt_domain" "sev_example" { + name = "sev-example" + memory = 2048 + vcpu = 2 + + launch_security { + type = "sev" + cbitpos = 47 + reduced_phys_bits = 1 + policy = 3 + } +} + +# SEV-SNP example +resource "libvirt_domain" "sev_snp_example" { + name = "sev-snp-example" + memory = 2048 + vcpu = 2 + + launch_security { + type = "sev-snp" + cbitpos = 51 + reduced_phys_bits = 1 + policy = 7 + kernel_hashes = "enabled" + } +} + +# S390-PV example +resource "libvirt_domain" "s390_pv_example" { + name = "s390-pv-example" + memory = 2048 + vcpu = 2 + + launch_security { + type = "s390-pv" + } +} \ No newline at end of file diff --git a/examples/v0.13/memory_backing/main.tf b/examples/v0.13/memory_backing/main.tf new file mode 100644 index 000000000..9201112bd --- /dev/null +++ b/examples/v0.13/memory_backing/main.tf @@ -0,0 +1,35 @@ +# Configure the provider +provider "libvirt" { + uri = "qemu:///system" +} + +# Simple memfd example +resource "libvirt_domain" "memfd_example" { + name = "memfd-example" + memory = 2048 + vcpu = 2 + + memory_backing { + source_type = "memfd" + } +} + +# Complex memory backing example +resource "libvirt_domain" "memory_backing_example" { + name = "memory-backing-example" + memory = 4096 + vcpu = 4 + + memory_backing { + source_type = "memfd" + access_mode = "shared" + allocation_mode = "immediate" + discard = true + locked = true + + hugepages { + size = 2048 + nodeset = "0-1" + } + } +} \ No newline at end of file diff --git a/libvirt/resource_libvirt_domain.go b/libvirt/resource_libvirt_domain.go index e71270285..0d28f7a5b 100644 --- a/libvirt/resource_libvirt_domain.go +++ b/libvirt/resource_libvirt_domain.go @@ -16,6 +16,7 @@ import ( "github.com/dmacvicar/terraform-provider-libvirt/libvirt/helper/suppress" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "libvirt.org/go/libvirtxml" ) @@ -480,10 +481,251 @@ func resourceLibvirtDomain() *schema.Resource { }, }, }, + "launch_security": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "sev", "sev-snp", "s390-pv", + }, false), + Description: "Launch security type (sev, sev-snp, or s390-pv)", + }, + // SEV specific settings + "cbitpos": { + Type: schema.TypeInt, + Optional: true, + Description: "C-bit position for SEV", + }, + "reduced_phys_bits": { + Type: schema.TypeInt, + Optional: true, + Description: "Reduced physical address bits for SEV", + }, + "policy": { + Type: schema.TypeInt, + Optional: true, + Description: "Policy value for SEV", + }, + "dh_cert": { + Type: schema.TypeString, + Optional: true, + Description: "Diffie-Hellman certificate for SEV", + }, + "session": { + Type: schema.TypeString, + Optional: true, + Description: "Session information for SEV", + }, + // SEV-SNP specific settings + "kernel_hashes": { + Type: schema.TypeString, + Optional: true, + Description: "Kernel hashes for SEV-SNP", + }, + "author_key": { + Type: schema.TypeString, + Optional: true, + Description: "Author key for SEV-SNP", + }, + "vcek": { + Type: schema.TypeString, + Optional: true, + Description: "VCEK for SEV-SNP", + }, + "guest_visible_workarounds": { + Type: schema.TypeString, + Optional: true, + Description: "Guest visible workarounds for SEV-SNP", + }, + "id_block": { + Type: schema.TypeString, + Optional: true, + Description: "ID block for SEV-SNP", + }, + "id_auth": { + Type: schema.TypeString, + Optional: true, + Description: "ID auth for SEV-SNP", + }, + "host_data": { + Type: schema.TypeString, + Optional: true, + Description: "Host data for SEV-SNP", + }, + }, + }, + }, + "memory_backing": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "file", "anonymous", "memfd", + }, false), + Description: "Memory backing source type (file, anonymous, or memfd)", + }, + "access_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "shared", "private", + }, false), + Description: "Memory access mode (shared or private)", + }, + "allocation_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "immediate", "ondemand", + }, false), + Description: "Memory allocation mode (immediate or ondemand)", + }, + "discard": { + Type: schema.TypeBool, + Optional: true, + Description: "Enable memory discard", + }, + "nosharepages": { + Type: schema.TypeBool, + Optional: true, + Description: "Disable memory sharing between guests", + }, + "locked": { + Type: schema.TypeBool, + Optional: true, + Description: "Lock memory to prevent swapping", + }, + "hugepages": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "size": { + Type: schema.TypeInt, + Required: true, + Description: "Huge page size in KiB", + }, + "nodeset": { + Type: schema.TypeString, + Optional: true, + Description: "NUMA nodes to allocate huge pages from", + }, + }, + }, + }, + }, + }, + }, }, } } +func setLaunchSecurity(d *schema.ResourceData, domainDef *libvirtxml.Domain) error { + if launchSecurityList, ok := d.GetOk("launch_security"); ok { + if len(launchSecurityList.([]interface{})) > 0 { + launchSecurity := launchSecurityList.([]interface{})[0].(map[string]interface{}) + secType := launchSecurity["type"].(string) + + // Initialize the launch security element + domainDef.LaunchSecurity = &libvirtxml.DomainLaunchSecurity{} + + switch secType { + case "sev": + sev := &libvirtxml.DomainLaunchSecuritySEV{} + + if v, ok := launchSecurity["cbitpos"]; ok && v.(int) > 0 { + cbitpos := uint(v.(int)) + sev.CBitPos = &cbitpos + } + + if v, ok := launchSecurity["reduced_phys_bits"]; ok && v.(int) > 0 { + reducedPhysBits := uint(v.(int)) + sev.ReducedPhysBits = &reducedPhysBits + } + + if v, ok := launchSecurity["policy"]; ok && v.(int) > 0 { + policy := uint(v.(int)) + sev.Policy = &policy + } + + if v, ok := launchSecurity["dh_cert"]; ok { + sev.DHCert = v.(string) + } + + if v, ok := launchSecurity["session"]; ok { + sev.Session = v.(string) + } + + domainDef.LaunchSecurity.SEV = sev + + case "sev-snp": + sevSnp := &libvirtxml.DomainLaunchSecuritySEVSNP{} + + if v, ok := launchSecurity["kernel_hashes"]; ok { + sevSnp.KernelHashes = v.(string) + } + + if v, ok := launchSecurity["author_key"]; ok { + sevSnp.AuthorKey = v.(string) + } + + if v, ok := launchSecurity["vcek"]; ok { + sevSnp.VCEK = v.(string) + } + + if v, ok := launchSecurity["cbitpos"]; ok && v.(int) > 0 { + cbitpos := uint(v.(int)) + sevSnp.CBitPos = &cbitpos + } + + if v, ok := launchSecurity["reduced_phys_bits"]; ok && v.(int) > 0 { + reducedPhysBits := uint(v.(int)) + sevSnp.ReducedPhysBits = &reducedPhysBits + } + + if v, ok := launchSecurity["policy"]; ok && v.(int) > 0 { + policy := uint64(v.(int)) + sevSnp.Policy = &policy + } + + if v, ok := launchSecurity["guest_visible_workarounds"]; ok { + sevSnp.GuestVisibleWorkarounds = v.(string) + } + + if v, ok := launchSecurity["id_block"]; ok { + sevSnp.IDBlock = v.(string) + } + + if v, ok := launchSecurity["id_auth"]; ok { + sevSnp.IDAuth = v.(string) + } + + if v, ok := launchSecurity["host_data"]; ok { + sevSnp.HostData = v.(string) + } + + domainDef.LaunchSecurity.SEVSNP = sevSnp + + case "s390-pv": + // S390 Protected Virtualization doesn't have any additional parameters + domainDef.LaunchSecurity.S390PV = &libvirtxml.DomainLaunchSecurityS390PV{} + } + } + } + + return nil +} + func resourceLibvirtDomainCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[DEBUG] Create resource libvirt_domain") @@ -561,6 +803,14 @@ func resourceLibvirtDomainCreate(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(err) } + if err := setMemoryBacking(d, &domainDef); err != nil { + return diag.FromErr(err) + } + + if err := setLaunchSecurity(d, &domainDef); err != nil { + return diag.FromErr(err) + } + var waitForLeases []*libvirtxml.DomainInterface partialNetIfaces := make(map[string]*pendingMapping, d.Get("network_interface.#").(int)) @@ -665,6 +915,83 @@ func resourceLibvirtDomainCreate(ctx context.Context, d *schema.ResourceData, me return nil } +func setMemoryBacking(d *schema.ResourceData, domainDef *libvirtxml.Domain) error { + if memBackingList, ok := d.GetOk("memory_backing"); ok { + if len(memBackingList.([]interface{})) > 0 { + memBacking := memBackingList.([]interface{})[0].(map[string]interface{}) + + // Initialize the memory backing element if not present + if domainDef.MemoryBacking == nil { + domainDef.MemoryBacking = &libvirtxml.DomainMemoryBacking{} + } + + // Set source type + if sourceType, ok := memBacking["source_type"].(string); ok && sourceType != "" { + if domainDef.MemoryBacking.MemorySource == nil { + domainDef.MemoryBacking.MemorySource = &libvirtxml.DomainMemorySource{} + } + domainDef.MemoryBacking.MemorySource.Type = sourceType + } + + // Set access mode + if accessMode, ok := memBacking["access_mode"].(string); ok && accessMode != "" { + if domainDef.MemoryBacking.MemoryAccess == nil { + domainDef.MemoryBacking.MemoryAccess = &libvirtxml.DomainMemoryAccess{} + } + domainDef.MemoryBacking.MemoryAccess.Mode = accessMode + } + + // Set allocation mode + if allocMode, ok := memBacking["allocation_mode"].(string); ok && allocMode != "" { + if domainDef.MemoryBacking.MemoryAllocation == nil { + domainDef.MemoryBacking.MemoryAllocation = &libvirtxml.DomainMemoryAllocation{} + } + domainDef.MemoryBacking.MemoryAllocation.Mode = allocMode + } + + // Set discard + if discard, ok := memBacking["discard"].(bool); ok && discard { + domainDef.MemoryBacking.MemoryDiscard = &libvirtxml.DomainMemoryDiscard{} + } + + // Set nosharepages + if nosharepages, ok := memBacking["nosharepages"].(bool); ok && nosharepages { + domainDef.MemoryBacking.MemoryNosharepages = &libvirtxml.DomainMemoryNosharepages{} + } + + // Set locked + if locked, ok := memBacking["locked"].(bool); ok && locked { + domainDef.MemoryBacking.MemoryLocked = &libvirtxml.DomainMemoryLocked{} + } + + // Set hugepages + if hugepagesList, ok := memBacking["hugepages"].([]interface{}); ok && len(hugepagesList) > 0 { + hugepages := &libvirtxml.DomainMemoryHugepages{ + Hugepages: []libvirtxml.DomainMemoryHugepage{}, + } + + for _, hpInterface := range hugepagesList { + hp := hpInterface.(map[string]interface{}) + + hugepage := libvirtxml.DomainMemoryHugepage{ + Size: uint(hp["size"].(int)), + } + + if nodeset, ok := hp["nodeset"].(string); ok && nodeset != "" { + hugepage.Nodeset = nodeset + } + + hugepages.Hugepages = append(hugepages.Hugepages, hugepage) + } + + domainDef.MemoryBacking.MemoryHugePages = hugepages + } + } + } + + return nil +} + func resourceLibvirtDomainUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[DEBUG] Update resource libvirt_domain") @@ -967,6 +1294,107 @@ func resourceLibvirtDomainRead(ctx context.Context, d *schema.ResourceData, meta d.Set("filesystem", filesystems) } + // Read launch security configuration + if domainDef.LaunchSecurity != nil { + launchSecurity := make(map[string]interface{}) + + if domainDef.LaunchSecurity.SEV != nil { + launchSecurity["type"] = "sev" + + if domainDef.LaunchSecurity.SEV.CBitPos != nil { + launchSecurity["cbitpos"] = int(*domainDef.LaunchSecurity.SEV.CBitPos) + } + + if domainDef.LaunchSecurity.SEV.ReducedPhysBits != nil { + launchSecurity["reduced_phys_bits"] = int(*domainDef.LaunchSecurity.SEV.ReducedPhysBits) + } + + if domainDef.LaunchSecurity.SEV.Policy != nil { + launchSecurity["policy"] = int(*domainDef.LaunchSecurity.SEV.Policy) + } + + launchSecurity["dh_cert"] = domainDef.LaunchSecurity.SEV.DHCert + launchSecurity["session"] = domainDef.LaunchSecurity.SEV.Session + + } else if domainDef.LaunchSecurity.SEVSNP != nil { + launchSecurity["type"] = "sev-snp" + + launchSecurity["kernel_hashes"] = domainDef.LaunchSecurity.SEVSNP.KernelHashes + launchSecurity["author_key"] = domainDef.LaunchSecurity.SEVSNP.AuthorKey + launchSecurity["vcek"] = domainDef.LaunchSecurity.SEVSNP.VCEK + + if domainDef.LaunchSecurity.SEVSNP.CBitPos != nil { + launchSecurity["cbitpos"] = int(*domainDef.LaunchSecurity.SEVSNP.CBitPos) + } + + if domainDef.LaunchSecurity.SEVSNP.ReducedPhysBits != nil { + launchSecurity["reduced_phys_bits"] = int(*domainDef.LaunchSecurity.SEVSNP.ReducedPhysBits) + } + + if domainDef.LaunchSecurity.SEVSNP.Policy != nil { + launchSecurity["policy"] = int(*domainDef.LaunchSecurity.SEVSNP.Policy) + } + + launchSecurity["guest_visible_workarounds"] = domainDef.LaunchSecurity.SEVSNP.GuestVisibleWorkarounds + launchSecurity["id_block"] = domainDef.LaunchSecurity.SEVSNP.IDBlock + launchSecurity["id_auth"] = domainDef.LaunchSecurity.SEVSNP.IDAuth + launchSecurity["host_data"] = domainDef.LaunchSecurity.SEVSNP.HostData + + } else if domainDef.LaunchSecurity.S390PV != nil { + launchSecurity["type"] = "s390-pv" + } + + d.Set("launch_security", []map[string]interface{}{launchSecurity}) + } + + // Read memory backing configuration + if domainDef.MemoryBacking != nil { + memBacking := make(map[string]interface{}) + + if domainDef.MemoryBacking.MemorySource != nil { + memBacking["source_type"] = domainDef.MemoryBacking.MemorySource.Type + } + + if domainDef.MemoryBacking.MemoryAccess != nil { + memBacking["access_mode"] = domainDef.MemoryBacking.MemoryAccess.Mode + } + + if domainDef.MemoryBacking.MemoryAllocation != nil { + memBacking["allocation_mode"] = domainDef.MemoryBacking.MemoryAllocation.Mode + } + + if domainDef.MemoryBacking.MemoryDiscard != nil { + memBacking["discard"] = true + } + + if domainDef.MemoryBacking.MemoryNosharepages != nil { + memBacking["nosharepages"] = true + } + + if domainDef.MemoryBacking.MemoryLocked != nil { + memBacking["locked"] = true + } + + if domainDef.MemoryBacking.MemoryHugePages != nil && len(domainDef.MemoryBacking.MemoryHugePages.Hugepages) > 0 { + hugepages := make([]map[string]interface{}, 0, len(domainDef.MemoryBacking.MemoryHugePages.Hugepages)) + + for _, hp := range domainDef.MemoryBacking.MemoryHugePages.Hugepages { + hugepage := make(map[string]interface{}) + hugepage["size"] = hp.Size + + if hp.Nodeset != "" { + hugepage["nodeset"] = hp.Nodeset + } + + hugepages = append(hugepages, hugepage) + } + + memBacking["hugepages"] = hugepages + } + + d.Set("memory_backing", []map[string]interface{}{memBacking}) + } + // lookup interfaces with addresses ifacesWithAddr, err := domainGetIfacesInfo(virConn, domain, d) if err != nil { diff --git a/libvirt/resource_libvirt_domain_test.go b/libvirt/resource_libvirt_domain_test.go index 550402a1f..e2215080d 100644 --- a/libvirt/resource_libvirt_domain_test.go +++ b/libvirt/resource_libvirt_domain_test.go @@ -1790,6 +1790,68 @@ func testAccCheckLibvirtDomainDestroy(s *terraform.State) error { return nil } +func TestAccLibvirtDomain_MemoryBackingMemfd(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtDomainDestroy, + Steps: []resource.TestStep{ + { + Config: ` + resource "libvirt_domain" "acceptance-test-memfd" { + name = "terraform-test-memfd" + memory_backing { + source_type = "memfd" + } + } + `, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-memfd", &libvirt.Domain{}), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memfd", "memory_backing.0.source_type", "memfd"), + ), + }, + }, + }) +} + +func TestAccLibvirtDomain_MemoryBackingComplex(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibvirtDomainDestroy, + Steps: []resource.TestStep{ + { + Config: ` + resource "libvirt_domain" "acceptance-test-memory" { + name = "terraform-test-memory" + memory_backing { + source_type = "memfd" + access_mode = "shared" + allocation_mode = "immediate" + discard = true + locked = true + hugepages { + size = 2048 + nodeset = "0-1" + } + } + } + `, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-memory", &libvirt.Domain{}), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.source_type", "memfd"), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.access_mode", "shared"), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.allocation_mode", "immediate"), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.discard", "true"), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.locked", "true"), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.hugepages.0.size", "2048"), + resource.TestCheckResourceAttr("libvirt_domain.acceptance-test-memory", "memory_backing.0.hugepages.0.nodeset", "0-1"), + ), + }, + }, + }) +} + func TestAccLibvirtDomain_FwCfgName(t *testing.T) { /* Ignition file is mounted of domain through `fw_cfg`. diff --git a/website/docs/r/domain.html.markdown b/website/docs/r/domain.html.markdown index 63a0b05d9..25410258d 100644 --- a/website/docs/r/domain.html.markdown +++ b/website/docs/r/domain.html.markdown @@ -65,6 +65,7 @@ The following arguments are supported: * `qemu_agent` (Optional) By default is disabled, set to true for enabling it. More info [qemu-agent](https://wiki.libvirt.org/page/Qemu_guest_agent). * `tpm` (Optional) TPM device to attach to the domain. The `tpm` object structure is documented [below](#tpm-device). * `type` (Optional) The type of hypervisor to use for the domain. Defaults to `kvm`, other values can be found [here](https://libvirt.org/formatdomain.html#id1) +* `memory_backing` (Optional) Configures how memory is managed for the domain. The `memory_backing` object structure is documented [below](#memory-backing). ### Kernel and boot arguments * `kernel` - (Optional) The path of the kernel to boot @@ -591,6 +592,101 @@ Additional attributes when `backend_type` is "emulator": * `backend_version` - (Optional) TPM version * `backend_persistent_state` - (Optional) Keep the TPM state when a transient domain is powered off or undefined +### Memory Backing + +The optional `memory_backing` block allows you to configure how memory is managed for the domain: + +```hcl +resource "libvirt_domain" "domain" { + # ... + + memory_backing { + source_type = "memfd" + access_mode = "shared" + allocation_mode = "immediate" + discard = true + locked = true + + hugepages { + size = 2048 + nodeset = "0-3" + } + } +} +``` + +Attributes: + +* `source_type` - (Optional) The memory backing source type. Can be one of: + * `file` - Memory is backed by files on the host + * `anonymous` - Memory is backed by anonymous memory + * `memfd` - Memory is backed by memory file descriptors (memfd) + +* `access_mode` - (Optional) Memory access mode. Can be one of: + * `shared` - Memory can be shared between guests + * `private` - Memory is private to this guest + +* `allocation_mode` - (Optional) Memory allocation mode. Can be one of: + * `immediate` - All memory is allocated upfront + * `ondemand` - Memory is allocated as needed + +* `discard` - (Optional) Enable memory discard (return unused memory to the host) + +* `nosharepages` - (Optional) Disable memory sharing between guests + +* `locked` - (Optional) Lock memory to prevent swapping + +* `hugepages` - (Optional) Configure huge pages for the domain + * `size` - (Required) Huge page size in KiB + * `nodeset` - (Optional) NUMA nodes to allocate huge pages from + +### Launch Security + +The `launch_security` block allows configuring secure boot features: + +```hcl +resource "libvirt_domain" "domain" { + # ... + + launch_security { + type = "sev" + cbitpos = 47 + reduced_phys_bits = 1 + policy = 3 + } +} +``` + +* `type` - (Required) The launch security type. Can be one of: + * `sev` - AMD Secure Encrypted Virtualization + * `sev-snp` - AMD SEV with Secure Nested Paging + * `s390-pv` - IBM S390 Protected Virtualization + +#### SEV Specific Settings + +These settings apply when `type` is set to `sev`: + +* `cbitpos` - (Optional) The C-bit position value +* `reduced_phys_bits` - (Optional) The number of reduced physical address bits +* `policy` - (Optional) The policy value +* `dh_cert` - (Optional) The Diffie-Hellman certificate +* `session` - (Optional) The session information + +#### SEV-SNP Specific Settings + +These settings apply when `type` is set to `sev-snp`: + +* `kernel_hashes` - (Optional) The kernel hashes +* `author_key` - (Optional) The author key +* `vcek` - (Optional) The VCEK value +* `cbitpos` - (Optional) The C-bit position value +* `reduced_phys_bits` - (Optional) The number of reduced physical address bits +* `policy` - (Optional) The policy value +* `guest_visible_workarounds` - (Optional) Guest visible workarounds +* `id_block` - (Optional) The ID block +* `id_auth` - (Optional) The ID auth +* `host_data` - (Optional) The host data + ### Altering libvirt's generated domain XML definition The optional `xml` block relates to the generated domain XML.