[SOLVED] Copy a Disk

utkonos

Active Member
Apr 11, 2022
152
49
33
My goal is to store a configuration file on a disk and then copy that disk to VMs that are being deployed.

The source VM has a disk in this state with the config file on it:

virtio1 = config:vm-100-disk-0,backup=0,size=1G

I am able to copy using the following process:

Code:
POST /api2/json/nodes/{node}/qemu/{src}/move_disk
disk = virtio1
storage = local-zfs

PUT /api2/json/nodes/{node}/qemu/{src}/config
delete = virtio1

POST /api2/json/nodes/{node}/qemu/{src}/move_disk
disk = unused1
target-disk = ide3
target-vmid = {dest}

PUT /api2/json/nodes/{node}/qemu/{src}/config
virtio1 = config:vm-100-disk-0,backup=0

This works, but it seems suboptimal to make four API calls to move a disk from one VM to another VM. Is there a better way?

I would like there to be one single API call and not need a second storage named "config" to have this all work. An ideal starting state would be:

virtio1 = local-zfs:100/vm-100-disk-1.qcow2,backup=0,size=1G

With the following being the ideal process for copying the disk:

Code:
POST /api2/json/nodes/{node}/qemu/{src}/move_disk
disk = virtio1
target-disk = ide3
target-vmid = {dest}
delete = 0

The end state for the source VM would be:

virtio1 = local-zfs:100/vm-100-disk-1.qcow2,backup=0,size=1G

The end state for the destination VM would be:

ide3 = local-zfs:200/vm-200-disk-1.raw,size=1G

In all of the above, the source {src} VM ID is 100 and the destination {dst} VM ID is 200.
 
Last edited:
You should research the new "import-from" functionality, it may help you. Other than that you are welcome to contribute new uber-API request if you feel its important :)

Code:
"import-from" : {
                                                   "description" : "Create a new disk, importing from this source (volume ID or absolute path). When an absolute path is specified, it's up to you to ensure that the source is not actively used by another process during the import!",
                                                   "format" : "pve-volume-id-or-absolute-path",
                                                   "format_description" : "source volume",
                                                   "optional" : 1,
                                                   "type" : "string"
                                                },


Blockbridge : Ultra low latency all-NVME shared storage for Proxmox - https://www.blockbridge.com/proxmox
 
  • Like
Reactions: utkonos
@bbgeek17 Thanks!

This gets me 95% of the way to where I want to go. There is still a cosmetic problem. I now import the disk to ide3 directly from the disk on the other VM. This works great, but ide is assigned disk filenames before virtio, so the state of the new VM is the following:

Code:
ide3 = local-zfs:200/vm-200-disk-0.qcow2,backup=0,size=1G
virtio0 = local-zfs:200/vm-200-disk-1.qcow2,backup=0,size=4G

After the deployment is complete, I detach and then delete the disk with the config (ide3). The end state is vm-200-disk-1.qcow2 rather than vm-200-disk-0.qcow2 for the VMs main disk. I wonder if there is a way to reverse the order that disk filenames are assigned? I have a workaround which is to keep the storage named "config" and import the disk to that storage. Then the disk names are both zeros, and the resulting state of the deployed VM is as I want. This leaves the extra "config" storage as part of the whole process, but is not a big deal.
 
Last edited:
Python:
import requests

token_id = 'root@pam!apikey'
secret = '00000000-0000-0000-0000-000000000000'
headers = {'Authorization': f'PVEAPIToken={token_id}={secret}'}
node = 'node1'
hostname = f'{node}.example.com'
base_url = f'https://{hostname}:8006'

def create_src():
    vmid = 100
    api_ep = f'/api2/json/nodes/{node}/qemu'
    config = {'name': 'source',
              'vmid': vmid,
              'ostype': 'l26',
              'memory': 1024,
              'cores': 2,
              'scsihw': 'virtio-scsi-pci',
              'virtio0': 'local-zfs:1,backup=0,format=qcow2'}

    response = requests.post(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    return vmid

def create_dst():
    vmid = 101
    api_ep = f'/api2/json/nodes/{node}/qemu'
    config = {'name': 'destination',
              'vmid': vmid,
              'ostype': 'l26',
              'memory': 1024,
              'cores': 2,
              'scsihw': 'virtio-scsi-pci',
              'virtio0': 'local-zfs:4,backup=0,format=qcow2'}

    response = requests.post(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    return vmid

def config_disk(vmid):
    device = 'virtio1'
    api_ep = f'/api2/json/nodes/{node}/qemu/{vmid}/config'
    config = {device: 'config:1,backup=0'}

    response = requests.post(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    return device, f'config:vm-{vmid}-disk-0'

def copy_disk(disk, src, dest):
    device, disk_id = disk
    dest_storage = 'local-zfs'
    api_ep = f'/api2/json/nodes/{node}/qemu/{src}/move_disk'
    config = {'disk': device,
              'storage': dest_storage}

    response = requests.post(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    api_ep = f'/api2/json/nodes/{node}/qemu/{src}/config'
    config = {'delete': device}

    response = requests.put(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    dest_device = 'ide0'
    api_ep = f'/api2/json/nodes/{node}/qemu/{src}/move_disk'
    config = {'disk': 'unused1',
              'target-disk': dest_device,
              'target-vmid': dest}

    response = requests.post(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    api_ep = f'/api2/json/nodes/{node}/qemu/{src}/config'
    config = {device: f'{disk_id},backup=0'}

    response = requests.put(base_url + api_ep, headers=headers, json=config)
    print(response.json().get('data'))

    return dest_device, f'{dest_storage}:{dest}/vm-{dest}-disk-1.raw'

src = create_src()
dst = create_dst()
cd = config_disk(src)
dcd = copy_disk(cd, src, dst)

Here's the long way, just for posterity. I don't need a single API call since I can do the import-from parameter.