Authors:

Feature pages are design documents that developers have created while collaborating on oVirt.

Most of them are outdated, but provide historical design context.

They are not user documentation and should not be treated as such.

Documentation is available here.

Backup-Restore Disk-Snapshots

Summary

This feature is a complementary component for providing ability to backup and restore virtual machines. The feature introduces a support for downloading and uploading disk-snapshots, using the REST-API. Images transfer is facilitated using ovirt-imageio

The following backup/restore examples are facilitated using oVirt Python-SDK. oVirt REST-API/SDKs can be used in a similar manner.

Backup

Download a VM OVF

Full Example

In order to fetch a VM configuration data, ‘all_content’ flag should be specified. Then, it can be written into a new file - that’s the OVF. Note: The OVF contains sufficient information about disks and snapshots to restore the VM to original state (e.g. alias/description/date). However, it isn’t automatically parsed by the system, but rather should be done by the SDK user.

vm_name = 'myvm'
vm = vms_service.list(search="name=%s" % vm_name, all_content=True)[0]
ovf_filename = "%s.ovf" % vm.id
with open(ovf_filename, "wb") as ovf_file:
    ovf_file.write(vm.initialization.configuration.data)

Download Disks

Full Example

The following example demonstrates the procedure of downloading the non-active disk-snapshots of a specified disk. Hence, in order to include the active layer as well, either create a new snapshot for the disk in advance, or download the active disk-snapshot using theDownload Disk Example

In order to download the active

  • For each disk, iterate over its disk-snapshots and execute image transfer.

      # Set relevant disk and stroage domain IDs
      disk_id = 'ccdd6487-0a8f-40c8-9f45-40e0e2b30d79'
      sd_name = 'mydata'
    
      # Get a reference to the storage domains service:
      storage_domains_service = system_service.storage_domains_service()
    
      # Look up for the storage domain by name:
      storage_domain = storage_domains_service.list(search='name=%s' % sd_name)[0]
    
      # Get a reference to the storage domain service in which the disk snapshots reside:
      storage_domain_service = storage_domains_service.storage_domain_service(storage_domain.id)
    
      # Get a reference to the disk snapshots service:
      disk_snapshot_service = storage_domain_service.disk_snapshots_service()
    
      # Get a list of disk snapshots by a disk ID
      all_disk_snapshots = disk_snapshot_service.list()
    
      # Filter disk snapshots list by disk id
      disk_snapshots = [s for s in all_disk_snapshots if s.disk.id == disk_id]
    
      # Download disk snapshots
      for disk_snapshot in disk_snapshots:
          download_disk_snapshot(disk_snapshot)
    
  • The ImageTransfer object should be specified with a ‘snapshot’ attribute.

      transfer = transfers_service.add(
          types.ImageTransfer(
              snapshot=types.DiskSnapshot(id=disk_snapshot_id),
              direction=types.ImageTransferDirection.DOWNLOAD,
          )
      )
    

Restore

Full Example

Compose disk-snapshots chain

Using the saved images files, create a chain of the disk-snapshots. The chain is built by fetching the volume info (using qemu-img) of each file, and find the backing filename (volume’s parent). By maintaing a map between parent and child volumes, we can construct the chain, starting from base volume.

    volumes_info = {}   # {filename -> vol_info}
    backing_files = {}  # {backing_file (parent) -> vol_info (child)}
    for root, dirs, file_names in os.walk(disk_path):
        for file_name in file_names:
            volume_info = get_volume_info("%s/%s" % (disk_path, file_name))
            volumes_info[file_name] = volume_info
            if 'full-backing-filename' in volume_info:
                backing_files[volume_info['full-backing-filename']] = volume_info

    base_volume = [v for v in volumes_info.values() if 'full-backing-filename' not in v ][0]
    child = backing_files[base_volume['filename']]
    images_chain = [base_volume]
    while child != None:
        images_chain.append(child)
        parent = child
        if parent['filename'] in backing_files:
            child = backing_files[parent['filename']]
        else:
            child = None

    return images_chain

Create disks

For each disk, invoke disk creation for the base image using the generated images chain from previous step. The content of the disks will be uploaded in a later step.

    base_image = images_chain[0]
    initial_size = base_image['actual-size']
    provisioned_size = base_image['virtual-size']
    image_id = os.path.basename(base_image['filename'])

    disk = disks_service.add(
        types.Disk(
            id=disk_id,
            image_id=image_id,
            name=disk_id,
            format=types.DiskFormat.RAW,
            provisioned_size=provisioned_size,
            initial_size=initial_size,
            storage_domains=[
                types.StorageDomain(
                    name=sd_name
                )
            ]
        )
    )

Add a VM from OVF

Adding the saved VM is done by specifing the ovf data within the configuration element.

    ovf_file_path = 'c3a8e806-106d-4aff-b59a-3a113eabf5a9.ovf'
    ovf_data = open(ovf_file_path, 'r').read()
    vms_service = system_service.vms_service()
    vm = vms_service.add(
        types.Vm(
            cluster=types.Cluster(
                name='Default',
            ),
            initialization = types.Initialization(
                configuration = types.Configuration(
                    type = types.ConfigurationType.OVF,
                    data = ovf_data
                )
            ),
        ),
    )

Create Snapshots

For each disk-snapshot in the chain, create a snapshot using the disk ID and image ID (and optionally the saved description).

    # Locate the service that manages the snapshots of the virtual machine:
    snapshots_service = vm_service.snapshots_service()

    # Add the new snapshot:
    snapshot = snapshots_service.add(
        types.Snapshot(
            description=description,
            disk_attachments=[
                types.DiskAttachment(
                    disk=types.Disk(
                        id=disk_id,
                        image_id=image_id
                    )
                )
            ]
        ),
    )

Upload disks

For each disk-snapshot in the chain, start an upload transfer.

    # Get a reference to the service that manages the image transfer:
    transfers_service = system_service.image_transfers_service()

    # Add a new image transfer:
    transfer = transfers_service.add(
        types.ImageTransfer(
            snapshot=types.DiskSnapshot(id=disk_snapshot_id),
            direction=types.ImageTransferDirection.UPLOAD,
        )
    )

    # Get reference to the created transfer service:
    transfer_service = transfers_service.image_transfer_service(transfer.id)

    while transfer.phase == types.ImageTransferPhase.INITIALIZING:
        time.sleep(1)
        transfer = transfer_service.get()

    try:
        proxy_url = urlparse(transfer.proxy_url)
        proxy_connection = get_proxy_connection(proxy_url)
        path = disk_path

        # Set needed headers for uploading:
        upload_headers = {
            'Authorization': transfer.signed_ticket,
        }

        with open(path, "rb") as disk:
            size = os.path.getsize(path)
            chunk_size = 1024 * 1024 * 8
            pos = 0
            while pos < size:
                # Extend the transfer session.
                transfer_service.extend()
                # Set the content range, according to the chunk being sent.
                upload_headers['Content-Range'] = "bytes %d-%d/%d" % (pos, min(pos + chunk_size, size) - 1, size)
                # Perform the request.
                proxy_connection.request(
                    'PUT',
                    proxy_url.path,
                    disk.read(chunk_size),
                    headers=upload_headers,
                )
                # Print response
                r = proxy_connection.getresponse()
                print r.status, r.reason, "Completed", "{:.0%}".format(pos / float(size))
                # Continue to next chunk.
                pos += chunk_size

        print "Completed", "{:.0%}".format(pos / float(size))
    finally:
        # Finalize the session.
        transfer_service.finalize()

Appendix

Python SDK examples