]> git.proxmox.com Git - pve-storage.git/commitdiff
imported from svn 'pve-storage/pve2'
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 23 Aug 2011 05:43:03 +0000 (07:43 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 23 Aug 2011 05:43:03 +0000 (07:43 +0200)
15 files changed:
ChangeLog [new file with mode: 0644]
Makefile [new file with mode: 0644]
PVE/API2/Makefile [new file with mode: 0644]
PVE/API2/Storage/Config.pm [new file with mode: 0755]
PVE/API2/Storage/Content.pm [new file with mode: 0644]
PVE/API2/Storage/Makefile [new file with mode: 0644]
PVE/API2/Storage/Scan.pm [new file with mode: 0644]
PVE/API2/Storage/Status.pm [new file with mode: 0644]
PVE/Makefile [new file with mode: 0644]
PVE/Storage.pm [new file with mode: 0755]
README [new file with mode: 0644]
changelog.Debian [new file with mode: 0644]
control.in [new file with mode: 0644]
copyright [new file with mode: 0644]
pvesm [new file with mode: 0755]

diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..9ca0794
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,293 @@
+2011-08-18  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (iscsi_login): login to target, instead of
+       portal-  to make it work when one portal is offline.
+
+2011-08-15  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (parse_config): fix parser for files without
+       newline at eof
+
+2011-08-12  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (scan_usb): imp.
+
+2011-08-05  Proxmox Support Team  <support@proxmox.com>
+
+       * changelog.Debian: increase release number to 2.0-4
+
+       * PVE/Storage.pm (iscsi_device_list): return numeric values for
+       channel/ID/LUN
+
+2011-08-01  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (iscsi_test_portal): factor out code to test if
+       portal in online (use 2 seconds timeout).
+       (iscsi_discovery): test if portal is online using
+       iscsi_test_portal(). This avoids that we run int a timeout (iscsi
+       default timeout is 15 seconds, we now use 2 seconds)
+       (cluster_lock_storage): fix cfs_lock_file() arguments,
+       (lock_storage_config): use default timeout (10)
+
+       * PVE/API2/Storage/Config.pm: s/resolv_portal_dns/resolv_portal/
+       (delete) do not call deactivate_storage(), because we likely run
+       into timeouts.
+
+       * PVE/Storage.pm (resolv_portal_dns): remove duplicate (use resolv_portal instead)
+       (resolv_portal): use resolv_server()
+
+       * PVE/API2/Storage/Scan.pm: remove unneccessary call to resolv_portal_dns()
+
+       * PVE/Storage.pm (iscsi_login): use Net::Ping to check portal
+       availability (avoid long iscsi login timeouts)
+       (resolv_portal_dns): use resolv_server()
+
+2011-07-29  Proxmox Support Team  <support@proxmox.com>
+
+       * changelog.Debian: update version to 2.0-3
+
+       * PVE/API2/Storage/Config.pm: activate base storage before we try
+       to create the VG. Make 'nodes' optional.
+
+2011-07-28  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (storage_check_node): check if storage is
+       available on a specific node.
+       (storage_check_enabled): check if storage is
+       available on the local node.
+       
+       * PVE/API2/Storage/Config.pm (create): add 'nodes' options, do not
+       activate storage automatically.
+       
+       * PVE/API2/Storage/Config.pm (update): add 'nodes' options, do not
+       activate storage automatically.
+
+       * pvesm (lock): removed - we do not use the central lock manager
+       anymore.
+
+       * PVE/Storage.pm (vdisk_alloc): use run_command() in order to get
+       better error messages.
+
+2011-07-27  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Storage/Config.pm (create): add option 'base' 
+
+2011-07-26  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (verify_portal_dns): new type
+       'pve-storage-portal-dns', which allows to use a DNS name.
+       (resolv_portal_dns): helper to convert portal with DNS name to IP address.
+
+       * PVE/API2/Storage/Config.pm: 'target' can be arbitrary string (we
+       do not check format for now)
+
+       * PVE/API2/Storage/Scan.pm (iscsiscan): rename 'server' to 'portal'
+
+2010-11-08  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (iscsi_login): multipath fixes: try to log in to all
+       portals (backport from stable)
+
+2010-10-28  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (iscsi_session_list): allow several sessions per
+       target (multipath)(backport from stable). 
+       (iscsi_session_rescan): rescan all sessions (backport from stable)
+
+2010-09-13  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (storage_info): cache VGs, mountdata and iSCSI
+       session list (backport from stable)
+
+2010-05-06  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (storage_migrate): use --sparse and --whole-file,
+       this alsocreates sparse files (backport from stable)
+
+2011-07-22  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Storage/Scan.pm: split scan into three different
+       methods with divverent return values
+
+2011-07-21  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (storage_info): do not list disabled storages
+
+2011-05-06  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/Storage/Status.pm: impl. content filter
+
+       * PVE/Storage.pm (storage_info): include content type
+
+2011-04-04  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (load_stable_scsi_paths): only load
+       /dev/disk/by-id once (avoid delays when we have many disks)
+
+2011-03-09  Proxmox Support Team  <support@proxmox.com>
+
+       * pvesm (status): report sizes like 'df'
+
+       * PVE/Storage.pm (file_size_info): allow to pass timeout
+       (important when NFS server is down)
+       (__activate_storage_full): avoid call to mkpath if not necessary
+       - avoid hang when NFS server is offline
+       (storage_info): return sizes in bytes
+       (storage_info): use PVE::Tools::df with timeout
+       (lvm_vgs): use '--units b' (report size in bytes)
+       (lvm_lvs): use '--units b' (report size in bytes)
+       (file_size_info): report size in bytes
+
+       * control.in (Depends): remove libfilesys-df-perl
+
+2011-03-08  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm (__activate_storage_full): avoid to create empty
+       content config
+
+2011-02-11  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/API2/*: cleanup API Object hierarchiy
+
+       * PVE/API2/Storage.pm: removed (no longer needed)
+
+2011-01-25  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm:  use new cfs_read_file/cfs_write_file everywhere
+       (cluster filesystem support)
+
+2010-11-08  Proxmox Support Team  <support@proxmox.com>
+
+       * PVE/Storage.pm: moved hostname read/write to INotify.pm
+
+2010-09-14  Proxmox Support Team  <support@proxmox.com>
+
+       * pvesm: add/use 'verifyapi' command
+
+       * Storage.pm (storage_info): better caching - avoid timeout bug
+       with large number of VGs.
+
+2010-09-07  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (parse_options): renamed from parse_options_new
+
+2010-08-26  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (vdisk_list): return full volid instead of volume name.
+       (template_list): return full volid instead of volume name.
+       (foreach_volid): re-add, slightly modified
+
+2010-08-25  Proxmox Support Team  <support@proxmox.com>
+
+       * pvesm: use new PVE::CLIHandler
+
+       * PVE/API2/Storage.pm: create extra upload method, because this
+       have different 'proxy' requirements that normal 'create'
+
+2010-08-24  Proxmox Support Team  <support@proxmox.com>
+
+       * pvesm: use new PVE::RPCEnvironment
+
+       * PVE/API2/*.pm: remove $conn parameter everywhere
+
+2010-08-19  Proxmox Support Team  <support@proxmox.com>
+
+       * pvesm: more cleanups - use new API calls
+
+2010-08-17  Proxmox Support Team  <support@proxmox.com>
+
+       * API2::Storage.pm: moved from pve-manager
+
+       * split API::Storage into different files
+       
+2010-08-16  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (file_read_firstline): import from PVE::Tools
+
+       * Storage.pm: use new INotify class
+
+       * Storage.pm (lock_config): renamed to lock_storage_config, use
+       lock_file from PVE::Utils
+
+       * control.in (Depends): add libpve-common-perl
+
+2010-07-16  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (parse_options): added ability to verify a
+       HASH (needed by REST API)
+
+2010-01-25  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (parse_lvm_name, parse_storage_id, parse_volume_id):
+       fix regex (allow 2 character names)
+
+2010-01-18  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (iscsi_device_list): fix for kernel 2.6.32
+
+2009-10-29  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (parse_volume_id): ignore case.
+
+2009-10-27  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (parse_volume_id): correctly parse storage id.
+
+2009-10-19  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (storage_migrate): flush output.
+
+2009-10-08  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (path): use  parse_volume_id()
+       (template_list): list backup files too
+
+2009-10-07  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (cluster_lock_storage): dont use ssh for local
+       request (master = localhost)
+
+2009-09-18  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (storage_remove): do not remove storage which is used
+       as base for other storage.
+
+2009-09-04  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (lvm_create_volume_group): don't set clustered flag
+       (vdisk_alloc): a better way to create unique disk names
+
+2009-08-21  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (activate_storage_list): only call udevsettle when
+       there are events. openvz container start/stop sometimes increases
+       event counter, but deliver no events. So udevsettle simply
+       hangs. Above optimization eliminate that bug in 99%.
+
+2009-08-20  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (cluster_lock_storage): implemented simply central
+       cluster lock manager.
+
+2009-08-18  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (iscsi_session_rescan): do not rescan uscsi too often
+       (wait at leaset 10 seconds).
+       (parse_storage_id): allow captial letters.
+
+2009-08-13  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm: use arrays instead of hash to return lists (SOAP
+       compatibility)
+       (foreach_volid): new helper method
+       (storage_migrate): first try
+
+2009-07-20  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (target_is_used): new function
+
+2009-07-03  Proxmox Support Team  <support@proxmox.com>
+
+       * Storage.pm (activate_storage_list): only call udev settle when
+       necessary (else it hangs sometimes)
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..484eb1c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,71 @@
+RELEASE=2.0
+
+VERSION=2.0
+PACKAGE=libpve-storage-perl
+PKGREL=4
+
+DESTDIR=
+PREFIX=/usr
+BINDIR=${PREFIX}/bin
+SBINDIR=${PREFIX}/sbin
+MANDIR=${PREFIX}/share/man
+DOCDIR=${PREFIX}/share/doc
+MAN1DIR=${MANDIR}/man1/
+export PERLDIR=${PREFIX}/share/perl5
+
+#ARCH:=$(shell dpkg-architecture -qDEB_BUILD_ARCH)
+ARCH=all
+DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb
+
+
+all: ${DEB}
+
+.PHONY: dinstall
+dinstall: deb
+       dpkg -i ${DEB}
+
+.PHONY: install
+install:
+       install -d ${DESTDIR}${SBINDIR}
+       install -m 0755 pvesm ${DESTDIR}${SBINDIR}
+       make -C PVE install
+       install -d ${DESTDIR}/usr/share/man/man1
+       pod2man -n pvesm -s 1 -r "proxmox 1.0" -c "Proxmox Documentation" <pvesm | gzip -9 > ${DESTDIR}/usr/share/man/man1/pvesm.1.gz
+
+.PHONY: deb ${DEB}
+deb ${DEB}:
+       rm -rf debian
+       mkdir debian
+       make DESTDIR=${CURDIR}/debian install
+       perl -I. ./pvesm verifyapi 
+       install -d -m 0755 debian/DEBIAN
+       sed -e s/@@VERSION@@/${VERSION}/ -e s/@@PKGRELEASE@@/${PKGREL}/ -e s/@@ARCH@@/${ARCH}/ <control.in >debian/DEBIAN/control
+       install -D -m 0644 copyright debian/${DOCDIR}/${PACKAGE}/copyright
+       install -m 0644 changelog.Debian debian/${DOCDIR}/${PACKAGE}/
+       gzip -9 debian/${DOCDIR}/${PACKAGE}/changelog.Debian
+       install -m 0644 ChangeLog debian/${DOCDIR}/${PACKAGE}/changelog
+       gzip -9 debian/${DOCDIR}/${PACKAGE}/changelog
+       dpkg-deb --build debian 
+       mv debian.deb ${DEB}
+       rm -rf debian
+       lintian ${DEB}
+
+.PHONY: clean
+clean:         
+       rm -rf debian *.deb ${PACKAGE}-*.tar.gz dist
+       find . -name '*~' -exec rm {} ';'
+
+.PHONY: distclean
+distclean: clean
+
+
+.PHONY: upload
+upload: ${DEB}
+       umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw 
+       mkdir -p /pve/${RELEASE}/extra
+       rm -f /pve/${RELEASE}/extra/${PACKAGE}_*.deb
+       rm -f /pve/${RELEASE}/extra/Packages*
+       cp ${DEB} /pve/${RELEASE}/extra
+       cd /pve/${RELEASE}/extra; dpkg-scanpackages . /dev/null > Packages; gzip -9c Packages > Packages.gz
+       umount /pve/${RELEASE}; mount /pve/${RELEASE} -o ro
+
diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile
new file mode 100644 (file)
index 0000000..e3ccdc1
--- /dev/null
@@ -0,0 +1,5 @@
+
+
+.PHONY: install
+install:
+       make -C Storage install
\ No newline at end of file
diff --git a/PVE/API2/Storage/Config.pm b/PVE/API2/Storage/Config.pm
new file mode 100755 (executable)
index 0000000..3a6c6bc
--- /dev/null
@@ -0,0 +1,329 @@
+package PVE::API2::Storage::Config;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Cluster qw(cfs_read_file cfs_write_file);
+use PVE::Storage;
+use HTTP::Status qw(:constants);
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+
+use Data::Dumper; # fixme: remove
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my @ctypes = qw(images vztmpl iso backup);
+
+my $storage_type_enum = ['dir', 'nfs', 'lvm', 'iscsi'];
+
+my $api_storage_config = sub {
+    my ($cfg, $storeid) = @_;
+
+    my $scfg = dclone(PVE::Storage::storage_config ($cfg, $storeid));
+    $scfg->{storage} = $storeid;
+    delete $scfg->{priority};
+    $scfg->{digest} = $cfg->{digest};
+    $scfg->{content} = PVE::Storage::content_hash_to_string($scfg->{content});
+
+    if ($scfg->{nodes}) {
+       $scfg->{nodes} = join(',', keys(%{$scfg->{nodes}}));
+    }
+
+    return $scfg;
+};
+
+__PACKAGE__->register_method ({
+    name => 'index', 
+    path => '',
+    method => 'GET',
+    description => "Storage index.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           type => { 
+               description => "Only list storage of specific type",
+               type => 'string', 
+               enum => $storage_type_enum,
+               optional => 1,
+           },
+
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { storage => { type => 'string'} },
+       },
+       links => [ { rel => 'child', href => "{storage}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = cfs_read_file("storage.cfg");
+
+       my @sids =  PVE::Storage::storage_ids($cfg);
+
+       my $res = [];
+       foreach my $storeid (@sids) {
+           my $scfg = &$api_storage_config($cfg, $storeid);
+           next if $param->{type} && $param->{type} ne $scfg->{type};
+           push @$res, $scfg;
+       }
+
+       return $res;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'read', 
+    path => '{storage}',
+    method => 'GET',
+    description => "Read storage configuration.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           storage => get_standard_option('pve-storage-id'),
+       },
+    },
+    returns => {},
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = cfs_read_file("storage.cfg");
+
+       return &$api_storage_config($cfg, $param->{storage});
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'create',
+    protected => 1,
+    path => '', 
+    method => 'POST',
+    description => "Create a new storage.",
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           storage => get_standard_option('pve-storage-id'),
+           nodes => get_standard_option('pve-node-list', { optional => 1 }),
+           type => { 
+               type => 'string', 
+               enum => $storage_type_enum,
+           },
+           path => {
+               type => 'string', format => 'pve-storage-path',
+               optional => 1,
+           },
+           export => {
+               type => 'string', format => 'pve-storage-path',
+               optional => 1,
+           },
+            server => {
+               type => 'string', format => 'pve-storage-server',
+               optional => 1,
+            },
+           options => {
+               type => 'string',  format => 'pve-storage-options',
+               optional => 1,
+           },
+            target => {
+               type => 'string',
+               optional => 1,
+            },
+            vgname => {
+               type => 'string', format => 'pve-storage-vgname',
+               optional => 1,
+            },
+           base => {
+               type => 'string', format => 'pve-volume-id',
+               optional => 1,
+           },
+            portal => {
+               type => 'string', format => 'pve-storage-portal-dns',
+               optional => 1,
+            },
+           content => {
+               type => 'string', format => 'pve-storage-content-list',
+               optional => 1,
+           },
+           disable => {
+               type => 'boolean',
+               optional => 1,
+           },
+           shared => {
+               type => 'boolean',
+               optional => 1,
+           },
+           'format' => { 
+               type => 'string', format => 'pve-storage-format',
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $type = $param->{type};
+       delete $param->{type};
+
+       my $storeid = $param->{storage};
+       delete $param->{storage};
+
+       if ($param->{portal}) {
+           $param->{portal} = PVE::Storage::resolv_portal($param->{portal});
+       }
+
+       my $opts = PVE::Storage::parse_options($storeid, $type, $param, 1);
+
+        PVE::Storage::lock_storage_config(
+           sub {
+
+               my $cfg = cfs_read_file('storage.cfg');
+
+               if (my $scfg = PVE::Storage::storage_config ($cfg, $storeid, 1)) {
+                   die "storage ID '$storeid' already defined\n";
+               }
+
+               $cfg->{ids}->{$storeid} = $opts;
+
+               if ($type eq 'lvm' && $opts->{base}) {
+
+                   my ($baseid, $volname) = PVE::Storage::parse_volume_id ($opts->{base});
+
+                   my $basecfg = PVE::Storage::storage_config ($cfg, $baseid, 1);
+                   die "base storage ID '$baseid' does not exist\n" if !$basecfg;
+       
+                   # we only support iscsi for now
+                   if (!($basecfg->{type} eq 'iscsi')) {
+                       die "unsupported base type '$basecfg->{type}'";
+                   }
+
+                   my $path = PVE::Storage::path ($cfg, $opts->{base});
+
+                   PVE::Storage::activate_storage($cfg, $baseid);
+
+                   PVE::Storage::lvm_create_volume_group ($path, $opts->{vgname}, $opts->{shared});
+               }
+
+               # try to activate if enabled on local node,
+               # we only do this to detect errors/problems sooner
+               if (PVE::Storage::storage_check_enabled($cfg, $storeid, undef, 1)) {
+                   PVE::Storage::activate_storage($cfg, $storeid);
+               }
+
+               cfs_write_file('storage.cfg', $cfg);
+           
+           }, "create storage failed");
+
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'update',
+    protected => 1,
+    path => '{storage}',
+    method => 'PUT',
+    description => "Update storage configuration.",
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           storage => get_standard_option('pve-storage-id'),
+           nodes => get_standard_option('pve-node-list', { optional => 1 }),
+           content => {
+               type => 'string', format => 'pve-storage-content-list',
+               optional => 1,
+           },
+           'format' => { 
+               type => 'string', format => 'pve-storage-format',
+               optional => 1,
+           },
+           disable => {
+               type => 'boolean',
+               optional => 1,
+           },
+           shared => {
+               type => 'boolean',
+               optional => 1,
+           },
+           options => {
+               type => 'string', format => 'pve-storage-options',
+               optional => 1,
+           },
+           digest => {
+               type => 'string',
+               optional => 1,
+           }
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $storeid = $param->{storage};
+       delete($param->{storage});
+       my $digest = $param->{digest};
+       delete($param->{digest});
+
+        PVE::Storage::lock_storage_config(
+        sub {
+
+           my $cfg = cfs_read_file('storage.cfg');
+
+           PVE::Storage::assert_if_modified ($cfg, $digest);
+
+           my $scfg = PVE::Storage::storage_config ($cfg, $storeid);
+
+           my $opts = PVE::Storage::parse_options($storeid, $scfg->{type}, $param);
+
+           foreach my $k (%$opts) {
+               $scfg->{$k} = $opts->{$k};
+           }
+
+           cfs_write_file('storage.cfg', $cfg);
+
+           }, "update storage failed");
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'delete',
+    protected => 1,
+    path => '{storage}', # /storage/config/{storage}
+    method => 'DELETE',
+    description => "Delete storage configuration.",
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           storage => get_standard_option('pve-storage-id'),
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $storeid = $param->{storage};
+       delete($param->{storage});
+        PVE::Storage::lock_storage_config(
+           sub {
+
+               my $cfg = cfs_read_file('storage.cfg');
+
+               die "can't remove storage - storage is used as base of another storage\n"
+                   if PVE::Storage::storage_is_used ($cfg, $storeid);
+
+               delete ($cfg->{ids}->{$storeid});
+
+               cfs_write_file('storage.cfg', $cfg);
+
+           }, "delete storage failed");
+  
+       return undef;
+    }});
+
+1;
diff --git a/PVE/API2/Storage/Content.pm b/PVE/API2/Storage/Content.pm
new file mode 100644 (file)
index 0000000..9f34819
--- /dev/null
@@ -0,0 +1,257 @@
+package PVE::API2::Storage::Content;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Cluster qw(cfs_read_file);
+use PVE::Storage;
+use PVE::INotify;
+use PVE::Exception qw(raise_param_exc);
+use PVE::RPCEnvironment;
+use PVE::RESTHandler;
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::RESTHandler);
+
+my @ctypes = qw(images vztmpl iso backup);
+
+__PACKAGE__->register_method ({
+    name => 'index', 
+    path => '',
+    method => 'GET',
+    description => "List storage content.",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id'),
+           content => { 
+               description => "Only list content of this type.",
+               type => 'string', format => 'pve-storage-content',
+               optional => 1,
+           },
+           vmid => get_standard_option
+               ('pve-vmid', { 
+                   description => "Only list images for this VM",
+                   optional => 1,              
+                }),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { 
+               volid => { 
+                   type => 'string' 
+               } 
+           },
+       },
+       links => [ { rel => 'child', href => "{volid}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $cts = $param->{content} ? [ $param->{content} ] : [ @ctypes ];
+
+       my $storeid = $param->{storage};
+
+       my $cfg = cfs_read_file("storage.cfg");
+
+       my $scfg = PVE::Storage::storage_config ($cfg, $storeid);
+
+       my $res = [];
+       foreach my $ct (@$cts) {
+           my $data;
+           if ($ct eq 'images') {
+               $data = PVE::Storage::vdisk_list ($cfg, $storeid, $param->{vmid});
+           } elsif ($ct eq 'iso') {
+               $data = PVE::Storage::template_list ($cfg, $storeid, 'iso') 
+                   if !$param->{vmid};
+           } elsif ($ct eq 'vztmpl') {
+               $data = PVE::Storage::template_list ($cfg, $storeid, 'vztmpl') 
+                   if !$param->{vmid};
+           } elsif ($ct eq 'backup') {
+               $data = PVE::Storage::template_list ($cfg, $storeid, 'backup') 
+                   if !$param->{vmid};
+           }
+
+           next if !$data || !$data->{$storeid};
+
+           foreach my $item (@{$data->{$storeid}}) {
+               push @$res, $item;
+           }
+       }
+
+       return $res;    
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'create', 
+    path => '',
+    method => 'POST',
+    description => "Allocate disk images.",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id'),
+           filename => { 
+               description => "The name of the file to create/upload.",
+               type => 'string',
+           },
+           vmid => get_standard_option('pve-vmid', { description => "Specify owner VM" } ),
+           size => {
+               description => "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
+               type => 'string',
+               pattern => '\d+[MG]?',
+           },
+           'format' => {
+               type => 'string',
+               enum => ['raw', 'qcow2'],
+               requires => 'size',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       description => "Volume identifier",
+       type => 'string',
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $storeid = $param->{storage};
+       my $name = $param->{filename};
+       my $sizestr = $param->{size};
+
+       my $size;
+       if ($sizestr =~ m/^\d+$/) {
+           $size = $sizestr;
+       } elsif ($sizestr =~ m/^(\d+)M$/) {
+           $size = $1 * 1024;
+       } elsif ($sizestr =~ m/^(\d+)G$/) {
+           $size = $1 * 1024 * 1024;
+       } else {
+           raise_param_exc({ size => "unable to parse size '$sizestr'" });
+       }
+
+       # extract FORMAT from name
+       if ($name =~ m/\.(raw|qcow2)$/) {
+           my $fmt = $1;
+
+           raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" }) 
+               if $param->{format} && $param->{format} ne $fmt;
+
+           $param->{format} = $fmt;
+       }
+
+       my $cfg = cfs_read_file('storage.cfg');
+    
+       my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid}, 
+                                              $param->{format}, 
+                                              $name, $size);
+
+       return $volid;
+    }});
+
+# we allow to pass volume names (without storage prefix) if the storage
+# is specified as separate parameter.
+my $real_volume_id = sub {
+    my ($storeid, $volume) = @_;
+
+    my $volid;
+
+    if ($volume =~ m/:/) {
+       eval {
+           my ($sid, $volname) = PVE::Storage::parse_volume_id ($volume);
+           raise_param_exc({ storage => "storage ID missmatch" }) 
+               if $storeid && $sid ne $storeid;
+           $volid = $volume;
+       };
+       raise_param_exc({ volume => $@}) if $@; 
+          
+    } else {
+       raise_param_exc({ volume => "no storage speficied - incomplete volume ID" }) 
+           if !$storeid;
+       
+       $volid = "$storeid:$volume";
+    }
+
+    return $volid;
+};
+
+__PACKAGE__->register_method ({
+    name => 'info',
+    path => '{volume}',
+    method => 'GET',
+    description => "Get volume attributes",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id', { optional => 1 }),
+           volume => {
+               description => "Volume identifier",
+               type => 'string', 
+           },
+       },
+    },
+    returns => { type => 'object' },
+    code => sub {
+       my ($param) = @_;
+
+       my $volid = &$real_volume_id($param->{storage}, $param->{volume});
+
+       my $cfg = cfs_read_file('storage.cfg');
+
+       my $path = PVE::Storage::path($cfg, $volid);
+       my ($size, $format, $used) = PVE::Storage::file_size_info ($path);
+
+       # fixme: return more attributes?
+       return {
+           path => $path,
+           size => $size,
+            used => $used,
+       };
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'delete',
+    path => '{volume}',
+    method => 'DELETE',
+    description => "Delete volume",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => { 
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id', { optional => 1}),
+           volume => {
+               description => "Volume identifier",
+               type => 'string', 
+           },
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $volid = &$real_volume_id($param->{storage}, $param->{volume});
+       
+       my $cfg = cfs_read_file('storage.cfg');
+
+       PVE::Storage::vdisk_free ($cfg, $volid);
+
+       return undef;
+    }});
+
+1;
diff --git a/PVE/API2/Storage/Makefile b/PVE/API2/Storage/Makefile
new file mode 100644 (file)
index 0000000..b23c17c
--- /dev/null
@@ -0,0 +1,6 @@
+
+SOURCES= Content.pm Status.pm Config.pm Scan.pm
+
+.PHONY: install
+install:
+       for i in ${SOURCES}; do install -D -m 0644 $$i ${DESTDIR}${PERLDIR}/PVE/API2/Storage/$$i; done
diff --git a/PVE/API2/Storage/Scan.pm b/PVE/API2/Storage/Scan.pm
new file mode 100644 (file)
index 0000000..3aaa066
--- /dev/null
@@ -0,0 +1,190 @@
+package PVE::API2::Storage::Scan;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Storage;
+use HTTP::Status qw(:constants);
+use PVE::JSONSchema qw(get_standard_option);
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+    name => 'index', 
+    path => '', 
+    method => 'GET',
+    description => "Index of available scan methods",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { method => { type => 'string'} },
+       },
+       links => [ { rel => 'child', href => "{method}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $res = [ 
+           { method => 'lvm' },
+           { method => 'iscsi' },
+           { method => 'nfs' },
+           { method => 'usb' },
+           ];
+
+       return $res;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'nfsscan', 
+    path => 'nfs', 
+    method => 'GET',
+    description => "Scan remote NFS server.",
+    protected => 1,
+    proxyto => "node",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           server => { type => 'string', format => 'pve-storage-server' },
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { 
+               path => { type => 'string'},
+               options => { type => 'string'},
+           },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $server = $param->{server};
+       my $res = PVE::Storage::scan_nfs($server);
+
+       my $data = [];
+       foreach my $k (keys %$res) {
+           push @$data, { path => $k, options => $res->{$k} };
+       }
+       return $data;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'iscsiscan', 
+    path => 'iscsi', 
+    method => 'GET',
+    description => "Scan remote iSCSI server.",
+    protected => 1,
+    proxyto => "node",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           portal => { type => 'string', format => 'pve-storage-portal-dns' },
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { 
+               target => { type => 'string'},
+               portal => { type => 'string'},
+           },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $res = PVE::Storage::scan_iscsi($param->{portal});
+
+       my $data = [];
+       foreach my $k (keys %$res) {
+           push @$data, { target => $k, portal => join(',', @{$res->{$k}}) };
+       }
+
+       return $data;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'lvmscan', 
+    path => 'lvm', 
+    method => 'GET',
+    description => "List local LVM volume groups.",
+    protected => 1,
+    proxyto => "node",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { 
+               vg => { type => 'string'},
+           },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $res = PVE::Storage::lvm_vgs();
+       return PVE::RESTHandler::hash_to_array($res, 'vg');
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'usbscan', 
+    path => 'usb', 
+    method => 'GET',
+    description => "List local USB devices.",
+    protected => 1,
+    proxyto => "node",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { 
+               busnum => { type => 'integer'},
+               devnum => { type => 'integer'},
+               port => { type => 'integer'},
+               usbpath => { type => 'string', optional => 1},
+               level => { type => 'integer'},
+               class => { type => 'integer'},
+               vendid => { type => 'string'},
+               prodid => { type => 'string'},
+               speed => { type => 'string'},
+
+               product => { type => 'string', optional => 1 },
+               serial => { type => 'string', optional => 1 },
+               manufacturer => { type => 'string', optional => 1 },
+           },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Storage::scan_usb();
+    }});
+
+1;
diff --git a/PVE/API2/Storage/Status.pm b/PVE/API2/Storage/Status.pm
new file mode 100644 (file)
index 0000000..509253d
--- /dev/null
@@ -0,0 +1,228 @@
+package PVE::API2::Storage::Status;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_read_file);
+use PVE::Storage;
+use PVE::API2::Storage::Content;
+use PVE::RESTHandler;
+use PVE::RPCEnvironment;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Exception qw(raise_param_exc);
+
+use base qw(PVE::RESTHandler);
+
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Storage::Content", 
+    # set fragment delimiter (no subdirs) - we need that, because volume
+    # IDs may contain a slash '/' 
+    fragmentDelimiter => '', 
+    path => '{storage}/content',
+});
+
+__PACKAGE__->register_method ({
+    name => 'index', 
+    path => '',
+    method => 'GET',
+    description => "Get status for all datastores.",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option
+               ('pve-storage-id', {
+                   description => "Only list status for  specified storage",
+                   optional => 1,
+                }),
+           content => { 
+               description => "Only list stores which support this content type.",
+               type => 'string', format => 'pve-storage-content',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { storage => { type => 'string' } },
+       },
+       links => [ { rel => 'child', href => "{storage}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = cfs_read_file("storage.cfg");
+
+       my $info = PVE::Storage::storage_info($cfg, $param->{content});
+
+       if ($param->{storage}) {
+           my $data = $info->{$param->{storage}};
+
+           raise_param_exc({ storage => "No such storage." })
+               if !defined($data);
+
+           $data->{storage} = $param->{storage};
+
+           return [ $data ];
+       }
+       return PVE::RESTHandler::hash_to_array($info, 'storage');
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'diridx',
+    path => '{storage}', 
+    method => 'GET',
+    description => "",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {
+               subdir => { type => 'string' },
+           },
+       },
+       links => [ { rel => 'child', href => "{subdir}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $res = [
+           { subdir => 'status' },
+           { subdir => 'content' },
+           { subdir => 'rrd' },
+           { subdir => 'rrddata' },
+           ];
+       
+       return $res;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'read_status',
+    path => '{storage}/status', 
+    method => 'GET',
+    description => "Read storage status.",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id'),
+       },
+    },
+    returns => {
+       type => "object",
+       properties => {},
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = cfs_read_file("storage.cfg");
+
+       my $info = PVE::Storage::storage_info($cfg, $param->{content});
+
+       my $data = $info->{$param->{storage}};
+
+       raise_param_exc({ storage => "No such storage." })
+           if !defined($data);
+    
+       return $data;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'rrd',
+    path => '{storage}/rrd', 
+    method => 'GET',
+    description => "Read storage RRD statistics (returns PNG).",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id'),
+           timeframe => {
+               description => "Specify the time frame you are interested in.",
+               type => 'string',
+               enum => [ 'hour', 'day', 'week', 'month', 'year' ],
+           },
+           ds => {
+               description => "The list of datasources you want to display.",
+               type => 'string', format => 'pve-configid-list',
+           },
+           cf => {
+               description => "The RRD consolidation function",
+               type => 'string',
+               enum => [ 'AVERAGE', 'MAX' ],
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => "object",
+       properties => {
+           filename => { type => 'string' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Cluster::create_rrd_graph(
+           "pve2-storage/$param->{node}/$param->{storage}", 
+           $param->{timeframe}, $param->{ds}, $param->{cf});
+                                             
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'rrddata',
+    path => '{storage}/rrddata', 
+    method => 'GET',
+    description => "Read storage RRD statistics.",
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id'),
+           timeframe => {
+               description => "Specify the time frame you are interested in.",
+               type => 'string',
+               enum => [ 'hour', 'day', 'week', 'month', 'year' ],
+           },
+           cf => {
+               description => "The RRD consolidation function",
+               type => 'string',
+               enum => [ 'AVERAGE', 'MAX' ],
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => "array",
+       items => {
+           type => "object",
+           properties => {},
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Cluster::create_rrd_data(
+           "pve2-storage/$param->{node}/$param->{storage}", 
+           $param->{timeframe}, $param->{cf});       
+    }});
+    
+1;
diff --git a/PVE/Makefile b/PVE/Makefile
new file mode 100644 (file)
index 0000000..ae63b2c
--- /dev/null
@@ -0,0 +1,6 @@
+
+
+.PHONY: install
+install:
+       install -D -m 0644 Storage.pm ${DESTDIR}${PERLDIR}/PVE/Storage.pm
+       make -C API2 install
\ No newline at end of file
diff --git a/PVE/Storage.pm b/PVE/Storage.pm
new file mode 100755 (executable)
index 0000000..5fe6c95
--- /dev/null
@@ -0,0 +1,2360 @@
+package PVE::Storage;
+
+use strict;
+use POSIX;
+use IO::Select;
+use IO::Dir;
+use IO::File;
+use Fcntl ':flock';
+use File::stat;
+use File::Basename;
+use File::Path;
+use IPC::Open2;
+use Cwd 'abs_path';
+use Getopt::Long qw(GetOptionsFromArray);
+use Socket;
+use Digest::SHA1;
+use Net::Ping;
+
+use PVE::Tools qw(run_command file_read_firstline trim);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Exception qw(raise_param_exc);
+use PVE::JSONSchema;
+use PVE::INotify;
+
+my $ISCSIADM = '/usr/bin/iscsiadm';
+my $UDEVADM = '/sbin/udevadm';
+
+$ISCSIADM = undef if ! -X $ISCSIADM;
+
+# fixme: always_call_parser => 1 ??
+cfs_register_file ('storage.cfg', 
+                  \&parse_config, 
+                  \&write_config); 
+
+# generic utility function
+
+sub config {
+    return cfs_read_file("storage.cfg");
+}
+
+sub check_iscsi_support {
+    my $noerr = shift;
+
+    if (!$ISCSIADM) {
+       my $msg = "no iscsi support - please install open-iscsi";
+       if ($noerr) {
+           warn "warning: $msg\n";
+           return 0;
+       }
+
+       die "error: $msg\n";
+    }
+
+    return 1;
+}
+
+sub load_stable_scsi_paths {
+
+    my $stable_paths = {};
+
+    my $stabledir = "/dev/disk/by-id";
+
+    if (my $dh = IO::Dir->new($stabledir)) {
+       while (defined(my $tmp = $dh->read)) {
+           # exclude filenames with part in name (same disk but partitions)
+           # use only filenames with scsi(with multipath i have the same device 
+          # with dm-uuid-mpath , dm-name and scsi in name)
+           if($tmp !~ m/-part\d+$/ && $tmp =~ m/^scsi-/) {
+                 my $path = "$stabledir/$tmp";
+                 my $bdevdest = readlink($path);
+                if ($bdevdest && $bdevdest =~ m|^../../([^/]+)|) {
+                    $stable_paths->{$1}=$tmp;
+                }
+          }
+       }
+       $dh->close;
+    }
+    return $stable_paths;
+}
+
+sub dir_glob_regex {
+    my ($dir, $regex) = @_;
+
+    my $dh = IO::Dir->new ($dir);
+    return wantarray ? () : undef if !$dh;
+  
+    while (defined(my $tmp = $dh->read)) { 
+       if (my @res = $tmp =~ m/^($regex)$/) {
+           $dh->close;
+           return wantarray ? @res : $tmp;
+       }
+    }
+    $dh->close;
+
+    return wantarray ? () : undef;
+}
+
+sub dir_glob_foreach {
+    my ($dir, $regex, $func) = @_;
+
+    my $dh = IO::Dir->new ($dir);
+    if (defined $dh) {
+       while (defined(my $tmp = $dh->read)) {
+           if (my @res = $tmp =~ m/^($regex)$/) {
+               &$func (@res);
+           }
+       }
+    } 
+}
+
+sub read_proc_mounts {
+    
+    local $/; # enable slurp mode
+    
+    my $data = "";
+    if (my $fd = IO::File->new ("/proc/mounts", "r")) {
+       $data = <$fd>;
+       close ($fd);
+    }
+
+    return $data;
+}
+
+#  PVE::Storage utility functions
+
+sub lock_storage_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file("storage.cfg", undef, $code);
+    my $err = $@;
+    if ($err) {
+       $errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+my $confvars = {
+    path => 'path',
+    shared => 'bool',
+    disable => 'bool',
+    format => 'format',
+    content => 'content',
+    server => 'server',
+    export => 'path',
+    vgname => 'vgname',
+    base   => 'volume',
+    portal => 'portal',
+    target => 'target',
+    nodes => 'nodes',
+    options => 'options',
+};
+
+my $required_config = {
+    dir => ['path'],
+    nfs => ['path', 'server', 'export'],
+    lvm => ['vgname'],
+    iscsi => ['portal', 'target'],
+};
+
+my $fixed_config = {
+    dir => ['path'],
+    nfs => ['path', 'server', 'export'],
+    lvm => ['vgname', 'base'],
+    iscsi => ['portal', 'target'],
+};
+
+my $default_config = {
+    dir => {
+       path => 1,
+        nodes => 0,
+       shared => 0,
+       disable => 0,
+       content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 },
+                    { images => 1,  rootdir => 1 }],
+       format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
+    },
+
+    nfs => {
+       path => 1,
+        nodes => 0,
+       disable => 0,
+        server => 1,
+        export => 1,
+        options => 0,
+       content => [ { images => 1, iso => 1, backup => 1},
+                    { images => 1 }],
+       format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
+    },
+
+    lvm => {
+       vgname => 1,
+        nodes => 0,
+       shared => 0,
+       disable => 0,
+       content => [ {images => 1}, { images => 1 }],
+        base => 1,
+    },
+
+    iscsi => {
+        portal => 1,
+        target => 1,
+        nodes => 0,
+       disable => 0,
+       content => [ {images => 1, none => 1}, { images => 1 }],
+    },
+};
+
+sub valid_content_types {
+    my ($stype) = @_;
+
+    my $def = $default_config->{$stype};
+
+    return {} if !$def;
+
+    return $def->{content}->[0];
+}
+
+sub content_hash_to_string {
+    my $hash = shift;
+
+    my @cta;
+    foreach my $ct (keys %$hash) {
+       push @cta, $ct if $hash->{$ct};
+    } 
+
+    return join(',', @cta);
+}
+
+PVE::JSONSchema::register_format('pve-storage-path', \&verify_path);
+sub verify_path {
+    my ($path, $noerr) = @_;
+
+    # fixme: exclude more shell meta characters?
+    # we need absolute paths
+    if ($path !~ m|^/[^;\(\)]+|) {
+       return undef if $noerr;
+       die "value does not look like a valid absolute path\n";
+    }
+    return $path;
+}
+
+PVE::JSONSchema::register_format('pve-storage-server', \&verify_server);
+sub verify_server {
+    my ($server, $noerr) = @_;
+
+    # fixme: use better regex ?
+    # IP or DNS name
+    if ($server !~ m/^[[:alnum:]\-\.]+$/) {
+       return undef if $noerr;
+       die "value does not look like a valid server name or IP address\n";
+    }
+    return $server;
+}
+
+PVE::JSONSchema::register_format('pve-storage-portal', \&verify_portal);
+sub verify_portal {
+    my ($portal, $noerr) = @_;
+
+    # IP with optional port
+    if ($portal !~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/) {
+       return undef if $noerr;
+       die "value does not look like a valid portal address\n";
+    }
+    return $portal;
+}
+
+PVE::JSONSchema::register_format('pve-storage-portal-dns', \&verify_portal_dns);
+sub verify_portal_dns {
+    my ($portal, $noerr) = @_;
+
+    # IP or DNS name with optional port
+    if ($portal !~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[[:alnum:]\-\.]+)(:\d+)?$/) {
+       return undef if $noerr;
+       die "value does not look like a valid portal address\n";
+    }
+    return $portal;
+}
+
+PVE::JSONSchema::register_format('pve-storage-content', \&verify_content);
+sub verify_content {
+    my ($ct, $noerr) = @_;
+
+    my $valid_content = valid_content_types('dir'); # dir includes all types
+    if (!$valid_content->{$ct}) {
+       return undef if $noerr;
+       die "invalid content type '$ct'\n";
+    }
+
+    return $ct;
+}
+
+PVE::JSONSchema::register_format('pve-storage-format', \&verify_format);
+sub verify_format {
+    my ($fmt, $noerr) = @_;
+
+    if ($fmt !~ m/(raw|qcow2|vmdk)/) {
+       return undef if $noerr;
+       die "invalid format '$fmt'\n";
+    }
+
+    return $fmt;
+}
+
+PVE::JSONSchema::register_format('pve-storage-options', \&verify_options);
+sub verify_options {
+    my ($value, $noerr) = @_;
+
+    # mount options (see man fstab)
+    if ($value !~ m/^\S+$/) {
+       return undef if $noerr;
+       die "invalid options '$value'\n";
+    }
+
+    return $value;
+}
+
+sub check_type {
+    my ($stype, $ct, $key, $value, $storeid, $noerr) = @_;
+
+    my $def = $default_config->{$stype};
+
+    if (!$def) { # should not happen
+       return undef if $noerr; 
+       die "unknown storage type '$stype'\n"; 
+    }
+
+    if (!defined($def->{$key})) {
+       return undef if $noerr;
+       die "unexpected property\n";
+    }
+
+    if (!defined ($value)) {
+       return undef if $noerr;
+       die "got undefined value\n";
+    }
+
+    if ($value =~ m/[\n\r]/) {
+       return undef if $noerr;
+       die "property contains a line feed\n";
+    }
+
+    if ($ct eq 'bool') {
+       return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); 
+       return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); 
+       return undef if $noerr;
+       die "type check ('boolean') failed - got '$value'\n";   
+    } elsif ($ct eq 'options') {
+       return verify_options($value, $noerr);
+    } elsif ($ct eq 'path') {
+       return verify_path($value, $noerr);
+    } elsif ($ct eq 'server') {
+       return verify_server($value, $noerr);
+    } elsif ($ct eq 'vgname') {
+       return parse_lvm_name ($value, $noerr);
+    } elsif ($ct eq 'portal') {
+       return verify_portal($value, $noerr);
+    } elsif ($ct eq 'nodes') {
+       my $res = {};
+
+       foreach my $node (PVE::Tools::split_list($value)) {
+           if (PVE::JSONSchema::pve_verify_node_name($node, $noerr)) {
+               $res->{$node} = 1;
+           }
+       }
+
+       # no node restrictions for local storage
+       if ($storeid && $storeid eq 'local' && scalar(keys(%$res))) {
+           return undef if $noerr;
+           die "storage '$storeid' does not allow node restrictions\n";
+       }
+
+       return $res;
+    } elsif ($ct eq 'target') {
+       return $value;
+    } elsif ($ct eq 'string') {
+       return $value;
+    } elsif ($ct eq 'format') {
+       my $valid_formats = $def->{format}->[0];
+
+       if (!$valid_formats->{$value}) {
+           return undef if $noerr;
+           die "storage does not support format '$value'\n";
+       }
+
+       return $value;
+
+    } elsif ($ct eq 'content') {
+       my $valid_content = $def->{content}->[0];
+       
+       my $res = {};
+
+       foreach my $c (PVE::Tools::split_list($value)) {
+           if (!$valid_content->{$c}) {
+               return undef if $noerr;
+               die "storage does not support content type '$c'\n";
+           }
+           $res->{$c} = 1;
+       } 
+
+       # only local storage may have several content types
+       if ($res->{none} || !($storeid && $storeid eq 'local')) {
+           if (scalar (keys %$res) > 1) {
+               return undef if $noerr;
+               die "storage does not support multiple content types\n";
+           }
+       }
+
+       # no backup to local storage
+       if ($storeid && $storeid eq 'local' && $res->{backup}) {
+               return undef if $noerr;
+               die "storage 'local' does not support backups\n";
+       }
+
+       return $res;    
+    } elsif ($ct eq 'volume') {
+       return $value if parse_volume_id ($value, $noerr);
+    }
+
+    return undef if $noerr;
+    die "type check not implemented - internal error\n";
+}
+
+sub parse_config {
+    my ($filename, $raw) = @_;
+
+    my $ids = {};
+
+    my $sha1 = Digest::SHA1->new;
+
+    my $pri = 0;
+
+    while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
+       my $line = $1;
+
+       $sha1->add ($line); # compute digest
+
+       next if $line =~ m/^\#/;
+       next if $line =~ m/^\s*$/;
+
+       if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
+           my $storeid = $2;
+           my $type = $1;
+           my $ignore = 0;
+
+           if (!parse_storage_id ($storeid, 1)) {
+               $ignore = 1;
+               warn "ignoring storage '$storeid' - (illegal characters)\n";
+           } elsif (!$default_config->{$type}) {
+               $ignore = 1;
+               warn "ignoring storage '$storeid' (unsupported type '$type')\n";
+           } else {
+               $ids->{$storeid}->{type} = $type;
+               $ids->{$storeid}->{priority} = $pri++;
+           }
+
+           while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
+               $line = $1;
+
+               next if $line =~ m/^\#/;
+               last if $line =~ m/^\s*$/;
+
+               next if $ignore; # skip
+
+               if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) {
+                   my ($k, $v) = ($1, $3);
+                   if (my $ct = $confvars->{$k}) {
+                       $v = 1 if $ct eq 'bool' && !defined($v);
+                       eval {
+                           $ids->{$storeid}->{$k} = check_type ($type, $ct, $k, $v, $storeid);
+                       };
+                       warn "storage '$storeid' - unable to parse value of '$k': $@" if $@;
+                   } else {
+                       warn "storage '$storeid' - unable to parse value of '$k'\n";
+                   }
+
+               } else {
+                   warn "storage '$storeid' - ignore config line: $line\n";
+               }
+           }
+       } else {
+           warn "ignore config line: $line\n";
+       }
+    }
+
+    # make sure we have a reasonable 'local:' storage
+    # openvz expects things to be there
+    if (!$ids->{local} || $ids->{local}->{type} ne 'dir' ||
+       $ids->{local}->{path} ne '/var/lib/vz') {
+       $ids->{local} = {
+           type => 'dir',
+           priority => $pri++,
+           path => '/var/lib/vz',    
+           content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1},
+       };
+    }
+
+    # we always need this for OpenVZ
+    $ids->{local}->{content}->{rootdir} = 1;
+    $ids->{local}->{content}->{vztmpl} = 1;
+    delete ($ids->{local}->{disable});
+
+    # remove node restrictions for local storage
+    delete($ids->{local}->{nodes});
+
+    foreach my $storeid (keys %$ids) {
+       my $d = $ids->{$storeid};
+
+       my $req_keys = $required_config->{$d->{type}};
+       foreach my $k (@$req_keys) {
+           if (!defined ($d->{$k})) {
+               warn "ignoring storage '$storeid' - missing value " .
+                   "for required option '$k'\n";
+               delete $ids->{$storeid};
+               next;           
+           }
+       }
+
+       my $def = $default_config->{$d->{type}};
+
+       if ($def->{content}) {
+           $d->{content} = $def->{content}->[1] if !$d->{content};
+       }
+
+       if ($d->{type} eq 'iscsi' || $d->{type} eq 'nfs') {
+           $d->{shared} = 1;
+       }
+    }
+
+    my $digest = $sha1->hexdigest;
+
+    my $cfg = { ids => $ids, digest => $digest};
+
+    return $cfg;
+}
+
+sub parse_options {
+    my ($storeid, $stype, $param, $create) = @_;
+
+    my $settings = { type => $stype };
+
+    die "unknown storage type '$stype'\n"
+       if !$default_config->{$stype};
+
+    foreach my $opt (keys %$param) {
+       my $value = $param->{$opt};
+
+       my $ct = $confvars->{$opt};
+       if (defined($value)) {
+           eval {
+               $settings->{$opt} = check_type ($stype, $ct, $opt, $value, $storeid);
+           };
+           raise_param_exc({ $opt => $@ }) if $@;
+       } else {
+           raise_param_exc({ $opt => "got undefined value" });
+       }
+    }
+
+    if ($create) {
+       my $req_keys = $required_config->{$stype};
+       foreach my $k (@$req_keys) {
+
+           if ($stype eq 'nfs' && !$settings->{path}) {
+               $settings->{path} = "/mnt/pve/$storeid";
+           }
+
+           # check if we have a value for all required options
+           if (!defined ($settings->{$k})) {
+               raise_param_exc({ $k => "property is missing and it is not optional" });
+           }
+       }
+    } else {
+       my $fixed_keys = $fixed_config->{$stype};
+       foreach my $k (@$fixed_keys) {
+
+           # only allow to change non-fixed values
+           
+           if (defined ($settings->{$k})) {
+               raise_param_exc({$k => "can't change value (fixed parameter)"});
+           }
+       }
+    }
+
+    return $settings;
+}
+
+sub cluster_lock_storage {
+    my ($storeid, $shared, $timeout, $func, @param) = @_;
+
+    my $res;
+    if (!$shared) {
+       my $lockid = "pve-storage-$storeid";
+       my $lockdir = "/var/lock/pve-manager";
+       mkdir $lockdir;
+       $res = PVE::Tools::lock_file("$lockdir/$lockid", $timeout, $func, @param); 
+       die $@ if $@;
+    } else {
+       $res = PVE::Cluster::cfs_lock_storage($storeid, $timeout, $func, @param);
+       die $@ if $@;
+    }  
+    return $res;
+}
+
+sub storage_config {
+    my ($cfg, $storeid, $noerr) = @_;
+
+    die "no storage id specified\n" if !$storeid;
+    my $scfg = $cfg->{ids}->{$storeid};
+
+    die "storage '$storeid' does not exists\n" if (!$noerr && !$scfg);
+
+    return $scfg;
+}
+
+sub storage_check_node {
+    my ($cfg, $storeid, $node, $noerr) = @_;
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    if ($scfg->{nodes}) {
+       $node = PVE::INotify::nodename() if !$node || ($node eq 'localhost');
+       if (!$scfg->{nodes}->{$node}) {
+           die "storage '$storeid' is not available on node '$node'" if !$noerr;
+           return undef;
+       }
+    }
+
+    return $scfg;
+}
+
+sub storage_check_enabled {
+    my ($cfg, $storeid, $node, $noerr) = @_;
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    if ($scfg->{disable}) {
+       die "storage '$storeid' is disabled\n" if !$noerr;
+       return undef;
+    }
+
+    return storage_check_node($cfg, $storeid, $node, $noerr);
+}
+
+sub storage_ids {
+    my ($cfg) = @_;
+
+    my $ids = $cfg->{ids};
+
+    my @sa = sort {$ids->{$a}->{priority} <=> $ids->{$b}->{priority}} keys %$ids;
+
+    return @sa;
+}
+
+sub assert_if_modified {
+    my ($cfg, $digest) = @_;
+
+    if ($digest && ($cfg->{digest} ne $digest)) {
+       die "detected modified storage configuration - try again\n";
+    }
+}
+
+sub sprint_config_line {
+    my ($k, $v) = @_;
+
+    my $ct = $confvars->{$k};
+
+    if ($ct eq 'bool') {
+       return $v ? "\t$k\n" : '';
+    } elsif ($ct eq 'nodes') {
+       my $nlist = join(',', keys(%$v));
+       return $nlist ? "\tnodes $nlist\n" : ''; 
+    } elsif ($ct eq 'content') {
+       my $clist = content_hash_to_string($v);
+       if ($clist) {
+           return "\t$k $clist\n";
+       } else {
+           return "\t$k none\n";
+       }
+    } else {
+       return "\t$k $v\n";
+    }
+}
+
+sub write_config {
+    my ($filename, $cfg) = @_;
+
+    my $out = '';
+
+    my $ids = $cfg->{ids};
+
+    my $maxpri = 0;
+    foreach my $storeid (keys %$ids) {
+       my $pri = $ids->{$storeid}->{priority}; 
+       $maxpri = $pri if $pri && $pri > $maxpri;
+    }
+    foreach my $storeid (keys %$ids) {
+       if (!defined ($ids->{$storeid}->{priority})) {
+           $ids->{$storeid}->{priority} = ++$maxpri;
+       } 
+    }
+
+    foreach my $storeid (sort {$ids->{$a}->{priority} <=> $ids->{$b}->{priority}} keys %$ids) {
+       my $scfg = $ids->{$storeid};
+       my $type = $scfg->{type};
+       my $def = $default_config->{$type};
+
+       die "unknown storage type '$type'\n" if !$def;
+
+       my $data = "$type: $storeid\n";
+
+       $data .= "\tdisable\n" if $scfg->{disable};
+
+       my $done_hash = { disable => 1};
+       foreach my $k (@{$required_config->{$type}}) {
+           $done_hash->{$k} = 1;
+           my $v =  $ids->{$storeid}->{$k};
+           die "storage '$storeid' - missing value for required option '$k'\n"
+               if !defined ($v);
+           $data .= sprint_config_line ($k, $v);
+       }
+
+       foreach my $k (keys %$def) {
+           next if defined ($done_hash->{$k});
+           if (defined (my $v = $ids->{$storeid}->{$k})) {
+               $data .= sprint_config_line ($k, $v);
+           }
+       }
+
+       $out .= "$data\n";
+    }
+
+    return $out;
+}
+
+sub get_image_dir {
+    my ($cfg, $storeid, $vmid) = @_;
+
+    my $path = $cfg->{ids}->{$storeid}->{path};
+    return $vmid ? "$path/images/$vmid" : "$path/images";
+}
+
+sub get_iso_dir {
+    my ($cfg, $storeid) = @_;
+
+    my $isodir =  $cfg->{ids}->{$storeid}->{path};
+    $isodir .= '/template/iso' if $storeid eq 'local';
+
+    return $isodir;
+}
+
+sub get_vztmpl_dir {
+    my ($cfg, $storeid) = @_;
+
+    my $tmpldir =  $cfg->{ids}->{$storeid}->{path};
+    $tmpldir .= '/template/cache' if $storeid eq 'local';
+
+    return $tmpldir;
+}
+
+# iscsi utility functions
+
+sub iscsi_session_list {
+
+    check_iscsi_support ();
+
+    my $cmd = [$ISCSIADM, '--mode', 'session'];
+
+    my $res = {};
+
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)\s*$/) {
+           my ($session, $target) = ($1, $2);
+           # there can be several sessions per target (multipath)
+           push @{$res->{$target}}, $session;
+
+       }
+    });
+
+    return $res;
+}
+
+sub iscsi_test_portal {
+    my ($portal) = @_;
+
+    my ($server, $port) = split(':', $portal);
+    my $p = Net::Ping->new("tcp", 2);
+    $p->port_number($port || 3260);
+    return $p->ping($server);
+}
+
+sub iscsi_discovery {
+    my ($portal) = @_;
+
+    check_iscsi_support ();
+
+    my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', 
+              '--portal', $portal];
+
+    my $res = {};
+
+    return $res if !iscsi_test_portal($portal); # fixme: raise exception here?
+
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       if ($line =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+)\,\S+\s+(\S+)\s*$/) {
+           my $portal = $1;
+           my $target = $2;
+           # one target can have more than one portal (multipath).
+           push @{$res->{$target}}, $portal;
+       }
+    });
+
+    return $res;
+}
+
+sub iscsi_login {
+    my ($target, $portal_in) = @_;
+
+    check_iscsi_support ();
+
+    eval { iscsi_discovery ($portal_in); };
+    warn $@ if $@;
+
+    my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname',  $target, '--login'];
+    run_command ($cmd);
+}
+
+sub iscsi_logout {
+    my ($target, $portal) = @_;
+
+    check_iscsi_support ();
+
+    my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout'];
+    run_command ($cmd);
+}
+
+my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
+
+sub iscsi_session_rescan {
+    my $session_list = shift;
+
+    check_iscsi_support ();
+
+    my $rstat = stat ($rescan_filename);
+
+    if (!$rstat) {
+       if (my $fh = IO::File->new ($rescan_filename, "a")) {
+           utime undef, undef, $fh;
+           close ($fh);
+       }
+    } else {
+       my $atime = $rstat->atime;
+       my $tdiff = time() - $atime;
+       # avoid frequent rescans
+       return if !($tdiff < 0 || $tdiff > 10);
+       utime undef, undef, $rescan_filename;
+    }
+
+    foreach my $session (@$session_list) {
+       my $cmd = [$ISCSIADM, '--mode', 'session', '-r', $session, '-R'];
+       eval { run_command ($cmd, outfunc => sub {}); };
+       warn $@ if $@;
+    }
+}
+
+sub iscsi_device_list {
+
+    my $res = {};
+
+    my $dirname = '/sys/class/iscsi_session';
+
+    my $stable_paths = load_stable_scsi_paths();
+
+    dir_glob_foreach ($dirname, 'session(\d+)', sub {
+       my ($ent, $session) = @_;
+
+       my $target = file_read_firstline ("$dirname/$ent/targetname");
+       return if !$target;
+
+       my (undef, $host) = dir_glob_regex ("$dirname/$ent/device", 'target(\d+):.*');
+       return if !defined($host);
+
+       dir_glob_foreach ("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
+           my ($tmp, $channel, $id, $lun) = @_;
+
+           my $type = file_read_firstline ("/sys/bus/scsi/devices/$tmp/type");
+           return if !defined($type) || $type ne '0'; # list disks only
+
+           my $bdev;
+           if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels
+               (undef, $bdev) = dir_glob_regex ("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
+           } else {
+               (undef, $bdev) = dir_glob_regex ("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
+           }
+           return if !$bdev;
+
+           #check multipath           
+           if (-d "/sys/block/$bdev/holders") { 
+               my $multipathdev = dir_glob_regex ("/sys/block/$bdev/holders", '[A-Za-z]\S*');
+               $bdev = $multipathdev if $multipathdev;
+           }
+
+           my $blockdev = $stable_paths->{$bdev};
+           return if !$blockdev;
+
+           my $size = file_read_firstline ("/sys/block/$bdev/size");
+           return if !$size;
+
+           my $volid = "$channel.$id.$lun.$blockdev";
+
+           $res->{$target}->{$volid} = {
+               'format' => 'raw', 
+               'size' => int($size / 2), 
+               'vmid' => 0, # not assigned to any vm
+               'channel' => int($channel),
+               'id' => int($id),
+               'lun' => int($lun),
+           };
+
+           #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n"; 
+       });
+
+    });
+
+    return $res;
+}
+
+# library implementation
+
+
+PVE::JSONSchema::register_format('pve-storage-id', \&parse_storage_id);
+sub parse_storage_id {
+    my ($storeid, $noerr) = @_;
+
+    if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
+       return undef if $noerr;
+       die "storage ID '$storeid' contains illegal characters\n";
+    }
+    return $storeid;
+}
+
+PVE::JSONSchema::register_standard_option('pve-storage-id', {
+    description => "The storage identifier.",
+    type => 'string', format => 'pve-storage-id',
+}); 
+
+PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name);
+sub parse_lvm_name {
+    my ($name, $noerr) = @_;
+
+    if ($name !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
+       return undef if $noerr;
+       die "lvm name '$name' contains illegal characters\n";
+    }
+
+    return $name;
+}
+
+sub parse_vmid {
+    my $vmid = shift;
+
+    die "VMID '$vmid' contains illegal characters\n" if $vmid !~ m/^\d+$/;
+
+    return int($vmid);
+}
+
+PVE::JSONSchema::register_format('pve-volume-id', \&parse_volume_id);
+sub parse_volume_id {
+    my ($volid, $noerr) = @_;
+
+    if ($volid =~ m/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):(.+)$/i) {
+       return wantarray ? ($1, $2) : $1;
+    }
+    return undef if $noerr;
+    die "unable to parse volume ID '$volid'\n";
+}
+
+sub parse_name_dir {
+    my $name = shift;
+
+    if ($name =~ m!^([^/\s]+\.(raw|qcow2|vmdk))$!) {
+       return ($1, $2);
+    }
+
+    die "unable to parse volume filename '$name'\n";
+}
+
+sub parse_volname_dir {
+    my $volname = shift;
+
+    if ($volname =~ m!^(\d+)/(\S+)$!) {
+       my ($vmid, $name) = ($1, $2);
+       parse_name_dir ($name);
+       return ('image', $name, $vmid);
+    } elsif ($volname =~ m!^iso/([^/]+\.[Ii][Ss][Oo])$!) {
+       return ('iso', $1);
+    } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.gz)$!) {
+       return ('vztmpl', $1);
+    }
+    die "unable to parse directory volume name '$volname'\n";
+}
+
+sub parse_volname_lvm {
+    my $volname = shift;
+
+    parse_lvm_name ($volname);
+
+    if ($volname =~ m/^(vm-(\d+)-\S+)$/) {
+       return ($1, $2);
+    }
+
+    die "unable to parse lvm volume name '$volname'\n";    
+}
+
+sub parse_volname_iscsi {
+    my $volname = shift;
+
+    if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
+       my $byid = $1;
+       return $byid;
+    }
+
+    die "unable to parse iscsi volume name '$volname'\n";
+}
+
+# try to map a filesystem path to a volume identifier
+sub path_to_volume_id {
+    my ($cfg, $path) = @_;
+
+    my $ids = $cfg->{ids};
+
+    my ($sid, $volname) = parse_volume_id ($path, 1);
+    if ($sid) {
+       if ($ids->{$sid} && (my $type = $ids->{$sid}->{type})) {
+           if ($type eq 'dir' || $type eq 'nfs') {
+               my ($vtype, $name, $vmid) = parse_volname_dir ($volname);
+               return ($vtype, $path);
+           }
+       }
+       return ('');
+    }
+
+    $path = abs_path ($path);
+
+    foreach my $sid (keys %$ids) {
+       my $type = $ids->{$sid}->{type};
+       next if !($type eq 'dir' || $type eq 'nfs');
+       
+       my $imagedir = $ids->{$sid}->{path} . "/images";
+       my $isodir = get_iso_dir ($cfg, $sid);
+       my $tmpldir = get_vztmpl_dir ($cfg, $sid);
+
+       if ($path =~ m!^$imagedir/(\d+)/([^/\s]+)$!) {
+           my $vmid = $1;
+           my $name = $2;
+           return ('image', "$sid:$vmid/$name");
+       } elsif ($path =~ m!^$isodir/([^/]+\.[Ii][Ss][Oo])$!) {
+           my $name = $1;
+           return ('iso', "$sid:iso/$name");   
+       } elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) {
+           my $name = $1;
+           return ('vztmpl', "$sid:vztmpl/$name");
+       }
+    }
+
+    # can't map path to volume id
+    return ('');
+}
+
+sub path {
+    my ($cfg, $volid) = @_;
+
+    my ($storeid, $volname) = parse_volume_id ($volid);
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    my $path;
+    my $owner;
+
+    if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+       my ($vtype, $name, $vmid) = parse_volname_dir ($volname);
+       $owner = $vmid;
+
+       my $imagedir = get_image_dir ($cfg, $storeid, $vmid);
+       my $isodir = get_iso_dir ($cfg, $storeid);
+       my $tmpldir = get_vztmpl_dir ($cfg, $storeid);
+
+       if ($vtype eq 'image') {
+           $path = "$imagedir/$name";
+       } elsif ($vtype eq 'iso') {
+           $path = "$isodir/$name";
+       } elsif ($vtype eq 'vztmpl') {
+           $path = "$tmpldir/$name";
+       } else {
+           die "should not be reached";
+       }
+
+    } elsif ($scfg->{type} eq 'lvm') {
+
+       my $vg = $scfg->{vgname};
+
+       my ($name, $vmid) = parse_volname_lvm ($volname);
+       $owner = $vmid;
+
+       $path = "/dev/$vg/$name";
+
+    } elsif ($scfg->{type} eq 'iscsi') {
+       my $byid = parse_volname_iscsi ($volname);
+       $path = "/dev/disk/by-id/$byid";
+    } else {
+       die "unknown storage type '$scfg->{type}'";
+    }
+
+    return wantarray ? ($path, $owner) : $path;
+}
+
+sub storage_migrate {
+    my ($cfg, $volid, $target_host, $target_storeid, $target_volname) = @_;
+
+    my ($storeid, $volname) = parse_volume_id ($volid);
+    $target_volname = $volname if !$target_volname;
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    # no need to migrate shared content
+    return if $storeid eq $target_storeid && $scfg->{shared};
+
+    my $tcfg = storage_config ($cfg, $target_storeid);
+
+    my $target_volid = "${target_storeid}:${target_volname}";
+
+    my $errstr = "unable to migrate '$volid' to '${target_volid}' on host '$target_host'";
+
+    # blowfish is a fast block cipher, much faster then 3des
+    my $sshoptions = "-c blowfish -o 'BatchMode=yes'";
+    my $ssh = "/usr/bin/ssh $sshoptions";
+
+    local $ENV{RSYNC_RSH} = $ssh;
+
+    if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+       if ($tcfg->{type} eq 'dir' || $tcfg->{type} eq 'nfs') {
+
+           my $src = path ($cfg, $volid);
+           my $dst = path ($cfg, $target_volid);
+
+           my $dirname = dirname ($dst);
+
+           if ($tcfg->{shared}) { # we can do a local copy
+               
+               run_command (['/bin/mkdir', '-p', $dirname]);
+
+               run_command (['/bin/cp', $src, $dst]);
+
+           } else {
+
+               run_command (['/usr/bin/ssh', "root\@${target_host}", 
+                             '/bin/mkdir', '-p', $dirname]);
+
+               # we use rsync with --sparse, so we can't use --inplace,
+               # so we remove file on the target if it already exists to
+               # save space
+               my ($size, $format) = file_size_info($src);
+               if ($format && ($format eq 'raw') && $size) {
+                   run_command (['/usr/bin/ssh', "root\@${target_host}", 
+                                 'rm', '-f', $dst],
+                                outfunc => sub {});
+               }
+
+               my $cmd = ['/usr/bin/rsync', '--progress', '--sparse', '--whole-file', 
+                          $src, "root\@${target_host}:$dst"];
+
+               my $percent = -1;
+
+               run_command ($cmd, outfunc => sub {
+                   my $line = shift;
+
+                   if ($line =~ m/^\s*(\d+\s+(\d+)%\s.*)$/) {
+                       if ($2 > $percent) {
+                           $percent = $2;
+                           print "rsync status: $1\n";
+                           *STDOUT->flush();
+                       }
+                   } else {
+                       print "$line\n";
+                       *STDOUT->flush();
+                   }
+               });
+           }
+
+
+       } else {
+
+           die "$errstr - target type '$tcfg->{type}' not implemented\n";
+       }
+
+    } else {
+       die "$errstr - source type '$scfg->{type}' not implemented\n";
+    }
+}
+
+sub vdisk_alloc {
+    my ($cfg, $storeid, $vmid, $fmt, $name, $size) = @_;
+
+    die "no storage id specified\n" if !$storeid;
+
+    parse_storage_id ($storeid);
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    die "no VMID specified\n" if !$vmid;
+
+    $vmid = parse_vmid ($vmid);
+
+    my $defformat = storage_default_format ($cfg, $storeid);
+
+    $fmt = $defformat if !$fmt;
+
+    activate_storage ($cfg, $storeid);
+
+    # lock shared storage
+    return cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
+
+       if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+
+           my $imagedir = get_image_dir ($cfg, $storeid, $vmid);
+
+           mkpath $imagedir;
+
+           if (!$name) {
+
+               for (my $i = 1; $i < 100; $i++) {
+                   my @gr = <$imagedir/vm-$vmid-disk-$i.*>;
+                   if (!scalar(@gr)) {
+                       $name = "vm-$vmid-disk-$i.$fmt";
+                       last;
+                   }
+               }
+           }
+
+           die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
+               if !$name;
+
+           my (undef, $tmpfmt) = parse_name_dir ($name);
+
+           die "illegal name '$name' - wrong extension for format ('$tmpfmt != '$fmt')\n" 
+               if $tmpfmt ne $fmt;
+
+           my $path = "$imagedir/$name";
+
+           die "disk image '$path' already exists\n" if -f $path;
+
+           run_command("/usr/bin/qemu-img create -f $fmt '$path' ${size}K", 
+                       errmsg => "unable to create image");
+
+           return "$storeid:$vmid/$name";
+
+       } elsif ($scfg->{type} eq 'lvm') {
+       
+           die "unsupported format '$fmt'" if $fmt ne 'raw';
+
+           die "illegal name '$name' - sould be 'vm-$vmid-*'\n" 
+               if  $name && $name !~ m/^vm-$vmid-/;
+
+           my $vgs = lvm_vgs ();
+
+           my $vg = $scfg->{vgname};
+
+           die "no such volume gruoup '$vg'\n" if !defined ($vgs->{$vg});
+
+           my $free = int ($vgs->{$vg}->{free});
+
+           die "not enough free space ($free < $size)\n" if $free < $size;
+
+           if (!$name) {
+               my $lvs = lvm_lvs ($vg);
+
+               for (my $i = 1; $i < 100; $i++) {
+                   my $tn = "vm-$vmid-disk-$i";
+                   if (!defined ($lvs->{$vg}->{$tn})) {
+                       $name = $tn;
+                       last;
+                   }
+               }
+           }
+
+           die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
+               if !$name;
+
+           my $cmd = ['/sbin/lvcreate', '--addtag', "pve-vm-$vmid", '--size', "${size}k", '--name', $name, $vg];
+
+           run_command ($cmd);
+
+           return "$storeid:$name";
+
+       } elsif ($scfg->{type} eq 'iscsi') {
+           die "can't allocate space in iscsi storage\n";
+       } else {
+           die "unknown storage type '$scfg->{type}'";
+       }
+    });
+}
+
+sub vdisk_free {
+    my ($cfg, $volid) = @_;
+
+    my ($storeid, $volname) = parse_volume_id ($volid);
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    activate_storage ($cfg, $storeid);
+
+    # lock shared storage
+    cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
+
+       if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+           my $path = path ($cfg, $volid); 
+
+           if (! -f $path) {
+               warn "disk image '$path' does not exists\n";
+           } else {
+               unlink $path;
+           }
+       } elsif ($scfg->{type} eq 'lvm') {
+
+           my $vg = $scfg->{vgname};
+
+           my $cmd = ['/sbin/lvremove', '-f', "$vg/$volname"];
+
+           run_command ($cmd);
+       } elsif ($scfg->{type} eq 'iscsi') {
+           die "can't free space in iscsi storage\n";
+       } else {
+           die "unknown storage type '$scfg->{type}'";
+       }
+    });
+}
+
+# lvm utility functions
+
+sub lvm_pv_info {
+    my ($device) = @_;
+
+    die "no device specified" if !$device;
+
+    my $has_label = 0;
+
+    my $cmd = ['/usr/bin/file', '-L', '-s', $device];
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+       $has_label = 1 if $line =~ m/LVM2/;
+    });
+
+    return undef if !$has_label;
+
+    $cmd = ['/sbin/pvs', '--separator', ':', '--noheadings', '--units', 'k',
+           '--unbuffered', '--nosuffix', '--options',
+           'pv_name,pv_size,vg_name,pv_uuid', $device];
+
+    my $pvinfo;
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       $line = trim($line);
+
+       my ($pvname, $size, $vgname, $uuid) = split (':', $line);
+
+       die "found multiple pvs entries for device '$device'\n" 
+           if $pvinfo;
+
+       $pvinfo = {
+           pvname => $pvname,
+           size => $size,
+           vgname => $vgname,
+           uuid => $uuid,
+       };
+    });
+
+    return $pvinfo;
+}
+
+sub clear_first_sector {
+    my ($dev) = shift;
+
+    if (my $fh = IO::File->new ($dev, "w")) {
+       my $buf = 0 x 512;
+       syswrite $fh, $buf;
+       $fh->close();   
+    }
+}
+
+sub lvm_create_volume_group {
+    my ($device, $vgname, $shared) = @_;
+    
+    my $res = lvm_pv_info ($device);
+    
+    if ($res->{vgname}) {
+       return if $res->{vgname} eq $vgname; # already created
+       die "device '$device' is already used by volume group '$res->{vgname}'\n";
+    }
+
+    clear_first_sector ($device); # else pvcreate fails
+
+    # we use --metadatasize 250k, which reseults in "pe_start = 512"
+    # so pe_start is aligned on a 128k boundary (advantage for SSDs)
+    my $cmd = ['/sbin/pvcreate', '--metadatasize', '250k', $device];
+
+    run_command ($cmd);
+
+    $cmd = ['/sbin/vgcreate', $vgname, $device];
+    # push @$cmd, '-c', 'y' if $shared; # we do not use this yet
+
+    run_command ($cmd);
+}
+
+sub lvm_vgs {
+
+    my $cmd = ['/sbin/vgs', '--separator', ':', '--noheadings', '--units', 'b',
+              '--unbuffered', '--nosuffix', '--options',
+              'vg_name,vg_size,vg_free'];
+
+    my $vgs = {};
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       $line = trim($line);
+
+       my ($name, $size, $free) = split (':', $line);
+
+       $vgs->{$name} = { size => int ($size), free => int ($free) };
+    });
+
+    return $vgs;
+}
+
+sub lvm_lvs {
+    my ($vgname) = @_;
+
+    my $cmd = ['/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b',
+              '--unbuffered', '--nosuffix', '--options',
+              'vg_name,lv_name,lv_size,uuid,tags'];
+
+    push @$cmd, $vgname if $vgname;
+
+    my $lvs = {};
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       $line = trim($line);
+
+       my ($vg, $name, $size, $uuid, $tags) = split (':', $line);
+
+       return if $name !~ m/^vm-(\d+)-/;
+       my $nid = $1;
+
+       my $owner;
+       foreach my $tag (split (/,/, $tags)) {
+           if ($tag =~ m/^pve-vm-(\d+)$/) {
+               $owner = $1;
+               last;
+           }
+       }
+       
+       if ($owner) {
+           if ($owner ne $nid) {
+               warn "owner mismatch name = $name, owner = $owner\n";
+           }
+   
+           $lvs->{$vg}->{$name} = { format => 'raw', size => $size, 
+                                    uuid => $uuid,  tags => $tags, 
+                                    vmid => $owner };
+       }
+    });
+
+    return $lvs;
+}
+
+#install iso or openvz template ($tt = <iso|vztmpl>)
+# we simply overwrite when file already exists
+sub install_template {
+    my ($cfg, $storeid, $tt, $srcfile, $destfile) = @_;
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    my $type = $scfg->{type};
+
+    die "invalid storage type '$type'" if !($type eq 'dir' || $type eq 'nfs');
+
+    my $path;
+
+    if ($tt eq 'iso') {
+       die "file '$destfile' has no '.iso' extension\n" 
+           if $destfile !~ m![^/]+\.[Ii][Ss][Oo]$!;
+       die "storage '$storeid' does not support 'iso' content\n" 
+           if !$scfg->{content}->{iso};
+       $path = get_iso_dir ($cfg, $storeid);
+    } elsif ($tt eq 'vztmpl') {
+       die "file '$destfile' has no '.tar.gz' extension\n"
+           if $destfile !~ m![^/]+\.tar\.gz$!;
+       die "storage '$storeid' does not support 'vztmpl' content\n" 
+           if !$scfg->{content}->{vztmpl};
+       $path = get_vztmpl_dir ($cfg, $storeid);
+    } else {
+       die "unknown template type '$tt'";
+    }
+
+    activate_storage ($cfg, $storeid);
+
+    my $dest = "$path/$destfile";
+
+    my $cmd = ['cp', $srcfile, $dest];
+
+    eval { run_command ($cmd); };
+    my $err = $@;
+
+    if ($err) {
+       unlink $dest;
+       die $err;
+    }
+}
+
+#list iso or openvz template ($tt = <iso|vztmpl|backup>)
+sub template_list {
+    my ($cfg, $storeid, $tt) = @_;
+
+    die "unknown template type '$tt'\n" if !($tt eq 'iso' || $tt eq 'vztmpl' || $tt eq 'backup'); 
+
+    my $ids = $cfg->{ids};
+
+    storage_check_enabled($cfg, $storeid) if ($storeid);
+
+    my $res = {};
+
+    # query the storage
+
+    foreach my $sid (keys %$ids) {
+       next if $storeid && $storeid ne $sid;
+
+       my $scfg = $ids->{$sid};
+       my $type = $scfg->{type};
+
+       next if !storage_check_enabled($cfg, $sid, undef, 1);
+
+       next if $tt eq 'iso' && !$scfg->{content}->{iso};
+       next if $tt eq 'vztmpl' && !$scfg->{content}->{vztmpl};
+       next if $tt eq 'backup' && !$scfg->{content}->{backup};
+
+       activate_storage ($cfg, $sid);
+
+       if ($type eq 'dir' || $type eq 'nfs') {
+
+           my $path;
+           if ($tt eq 'iso') {
+               $path = get_iso_dir ($cfg, $sid);
+           } elsif ($tt eq 'vztmpl') {
+               $path = get_vztmpl_dir ($cfg, $sid);
+           } elsif ($tt eq 'backup') {
+               $path = $scfg->{path};
+           } else {
+               die "unknown template type '$tt'\n";
+           }
+
+           foreach my $fn (<$path/*>) {
+
+               my $info;
+
+               if ($tt eq 'iso') {
+                   next if $fn !~ m!/([^/]+\.[Ii][Ss][Oo])$!;
+
+                   $info = { volid => "$sid:iso/$1", format => 'iso' };
+
+               } elsif ($tt eq 'vztmpl') {
+                   next if $fn !~ m!/([^/]+\.tar\.gz)$!;
+
+                   $info = { volid => "$sid:vztmpl/$1", format => 'tgz' };
+
+               } elsif ($tt eq 'backup') {
+                   next if $fn !~ m!/([^/]+\.(tar|tgz))$!;
+                   
+                   $info = { volid => "$sid:backup/$1", format => $2 };
+               }
+
+               $info->{size} = -s $fn;
+
+               push @{$res->{$sid}}, $info;
+           }
+
+       }
+
+       @{$res->{$sid}} = sort {lc($a->{volid}) cmp lc ($b->{volid}) } @{$res->{$sid}} if $res->{$sid};
+    }
+
+    return $res;
+}
+
+sub file_size_info {
+    my ($filename, $timeout) = @_;
+
+    my $cmd = ['/usr/bin/qemu-img', 'info', $filename];
+
+    my $format;
+    my $size = 0;
+    my $used = 0;
+
+    eval {
+       run_command ($cmd, timeout => $timeout, outfunc => sub {
+           my $line = shift;
+
+           if ($line =~ m/^file format:\s+(\S+)\s*$/) {
+               $format = $1;
+           } elsif ($line =~ m/^virtual size:\s\S+\s+\((\d+)\s+bytes\)$/) {
+               $size = int($1);
+           } elsif ($line =~ m/^disk size:\s+(\d+(.\d+)?)([KMGT])\s*$/) {
+               $used = $1;
+               my $u = $3;
+
+               $used *= 1024 if $u eq 'K';
+               $used *= (1024*1024) if $u eq 'M';
+               $used *= (1024*1024*1024) if $u eq 'G';
+               $used *= (1024*1024*1024*1024) if $u eq 'T';
+
+               $used = int($used);
+           }
+       });
+    };
+
+    return wantarray ? ($size, $format, $used) : $size;
+}
+
+sub vdisk_list {
+    my ($cfg, $storeid, $vmid, $vollist) = @_;
+
+    my $ids = $cfg->{ids};
+
+    storage_check_enabled($cfg, $storeid) if ($storeid);
+
+    my $res = {};
+
+    # prepare/activate/refresh all storages
+
+    my $stypes = {};
+
+    my $storage_list = [];
+    if ($vollist) {
+       foreach my $volid (@$vollist) {
+           my ($sid, undef) = parse_volume_id ($volid);
+           next if !defined ($ids->{$sid});
+           next if !storage_check_enabled($cfg, $sid, undef, 1);
+           push @$storage_list, $sid;
+           $stypes->{$ids->{$sid}->{type}} = 1;
+       }
+    } else {
+       foreach my $sid (keys %$ids) {
+           next if $storeid && $storeid ne $sid;
+           next if !storage_check_enabled($cfg, $sid, undef, 1);
+           push @$storage_list, $sid;
+           $stypes->{$ids->{$sid}->{type}} = 1;
+       }
+    }
+
+    activate_storage_list ($cfg, $storage_list);
+
+    my $lvs = $stypes->{lvm} ? lvm_lvs () : {};
+
+    my $iscsi_devices = iscsi_device_list() if $stypes->{iscsi};
+
+    # query the storage
+
+    foreach my $sid (keys %$ids) {
+       if ($storeid) {
+           next if $storeid ne $sid;
+           next if !storage_check_enabled($cfg, $sid, undef, 1);
+       }
+       my $scfg = $ids->{$sid};
+       my $type = $scfg->{type};
+
+       if ($type eq 'dir' || $type eq 'nfs') {
+
+           my $path = $scfg->{path};
+
+           my $fmts = join ('|', keys %{$default_config->{$type}->{format}->[0]}); 
+
+           foreach my $fn (<$path/images/[0-9][0-9]*/*>) {
+
+               next if $fn !~ m!^(/.+/images/(\d+)/([^/]+\.($fmts)))$!;
+               $fn = $1; # untaint
+
+               my $owner = $2;
+               my $name = $3;
+               my $volid = "$sid:$owner/$name";
+
+               if ($vollist) {
+                   my $found = grep { $_ eq $volid } @$vollist;
+                   next if !$found;
+               } else {
+                   next if defined ($vmid) && ($owner ne $vmid);
+               }
+
+               my ($size, $format, $used) = file_size_info ($fn);
+
+               if ($format && $size) {
+                   push @{$res->{$sid}}, { 
+                       volid => $volid, format => $format,
+                       size => $size, vmid => $owner, used => $used };
+               }
+
+           }
+
+       } elsif ($type eq 'lvm') {
+
+           my $vgname = $scfg->{vgname};
+
+           if (my $dat = $lvs->{$vgname}) {
+
+               foreach my $volname (keys %$dat) {
+
+                   my $owner = $dat->{$volname}->{vmid};
+
+                   my $volid = "$sid:$volname";
+
+                   if ($vollist) {
+                       my $found = grep { $_ eq $volid } @$vollist;
+                       next if !$found;
+                   } else {
+                       next if defined ($vmid) && ($owner ne $vmid);
+                   }
+
+                   my $info = $dat->{$volname};
+                   $info->{volid} = $volid;
+
+                   push @{$res->{$sid}}, $info;
+               }
+           }
+
+       } elsif ($type eq 'iscsi') {
+
+           # we have no owner for iscsi devices
+
+           my $target = $scfg->{target};
+
+           if (my $dat = $iscsi_devices->{$target}) {
+
+               foreach my $volname (keys %$dat) {
+
+                   my $volid = "$sid:$volname";
+
+                   if ($vollist) {
+                       my $found = grep { $_ eq $volid } @$vollist;
+                       next if !$found;
+                   } else {
+                       next if !($storeid && ($storeid eq $sid));
+                   }
+
+                   my $info = $dat->{$volname};
+                   $info->{volid} = $volid;
+
+                   push @{$res->{$sid}}, $info;
+               }
+           }
+
+       } else {
+           die "implement me";
+       }
+
+       @{$res->{$sid}} = sort {lc($a->{volid}) cmp lc ($b->{volid}) } @{$res->{$sid}} if $res->{$sid};
+    }
+
+    return $res;
+}
+
+sub nfs_is_mounted {
+    my ($server, $export, $mountpoint, $mountdata) = @_;
+
+    my $source = "$server:$export";
+
+    $mountdata = read_proc_mounts() if !$mountdata;
+
+    if ($mountdata =~ m/^$source\s$mountpoint\snfs/m) {
+       return $mountpoint;
+    } 
+
+    return undef;
+}
+
+sub nfs_mount {
+    my ($server, $export, $mountpoint, $options) = @_;
+
+    my $source = "$server:$export";
+
+    my $cmd = ['/bin/mount', '-t', 'nfs', $source, $mountpoint];
+    if ($options) {
+       push @$cmd, '-o', $options;
+    } 
+
+    run_command ($cmd);
+}
+
+sub uevent_seqnum {
+
+    my $filename = "/sys/kernel/uevent_seqnum";
+
+    my $seqnum = 0;
+    if (my $fh = IO::File->new ($filename, "r")) {
+       my $line = <$fh>;
+       if ($line =~ m/^(\d+)$/) {
+           $seqnum = int ($1);
+       }
+       close ($fh);
+    }
+    return $seqnum;
+}
+
+sub __activate_storage_full {
+    my ($cfg, $storeid, $session) = @_;
+
+    my $scfg = storage_check_enabled($cfg, $storeid);
+
+    return if $session->{activated}->{$storeid};
+
+    if (!$session->{mountdata}) {
+       $session->{mountdata} = read_proc_mounts();
+    }
+
+    if (!$session->{uevent_seqnum}) {
+       $session->{uevent_seqnum} = uevent_seqnum ();
+    }
+
+    my $mountdata = $session->{mountdata};
+
+    my $type = $scfg->{type};
+
+    if ($type eq 'dir' || $type eq 'nfs') {
+
+       my $path = $scfg->{path};
+
+       if ($type eq 'nfs') {
+           my $server = $scfg->{server};
+           my $export = $scfg->{export};
+
+           if (!nfs_is_mounted ($server, $export, $path, $mountdata)) {    
+                   
+               # NOTE: only call mkpath when not mounted (avoid hang 
+               # when NFS server is offline 
+                   
+               mkpath $path;
+
+               die "unable to activate storage '$storeid' - " .
+                   "directory '$path' does not exist\n" if ! -d $path;
+
+               nfs_mount ($server, $export, $path, $scfg->{options});
+           }
+
+       } else {
+
+           mkpath $path;
+
+           die "unable to activate storage '$storeid' - " .
+               "directory '$path' does not exist\n" if ! -d $path;
+       }
+
+       my $imagedir = get_image_dir ($cfg, $storeid);
+       my $isodir = get_iso_dir ($cfg, $storeid);
+       my $tmpldir = get_vztmpl_dir ($cfg, $storeid);
+
+       if (defined($scfg->{content})) {
+           mkpath $imagedir if $scfg->{content}->{images} &&
+               $imagedir ne $path;
+           mkpath $isodir if $scfg->{content}->{iso} &&
+               $isodir ne $path;
+           mkpath $tmpldir if $scfg->{content}->{vztmpl} &&
+               $tmpldir ne $path;
+       }
+
+    } elsif ($type eq 'lvm') {
+
+       if ($scfg->{base}) {
+           my ($baseid, undef) = parse_volume_id ($scfg->{base});
+           __activate_storage_full ($cfg, $baseid, $session);
+       }
+
+       if (!$session->{vgs}) {
+           $session->{vgs} = lvm_vgs();
+       }
+
+       # In LVM2, vgscans take place automatically;
+       # this is just to be sure
+       if ($session->{vgs} && !$session->{vgscaned} && 
+           !$session->{vgs}->{$scfg->{vgname}}) {
+           $session->{vgscaned} = 1;
+           my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
+           eval { run_command ($cmd, outfunc => sub {}); };
+           warn $@ if $@;
+       }
+
+       my $cmd = ['/sbin/vgchange', '-aly', $scfg->{vgname}];
+       run_command ($cmd, outfunc => sub {});
+
+    } elsif ($type eq 'iscsi') {
+
+       return if !check_iscsi_support(1);
+
+       $session->{iscsi_sessions} = iscsi_session_list()
+           if !$session->{iscsi_sessions};
+
+       my $iscsi_sess = $session->{iscsi_sessions}->{$scfg->{target}};
+       if (!defined ($iscsi_sess)) {
+           eval { iscsi_login ($scfg->{target}, $scfg->{portal}); };
+           warn $@ if $@;
+       } else {
+           # make sure we get all devices
+           iscsi_session_rescan ($iscsi_sess);
+       }
+
+    } else {
+       die "implement me";
+    }
+
+    my $newseq = uevent_seqnum ();
+
+    # only call udevsettle if there are events
+    if ($newseq > $session->{uevent_seqnum}) {
+       my $timeout = 30;
+       system ("$UDEVADM settle --timeout=$timeout"); # ignore errors
+       $session->{uevent_seqnum} = $newseq;
+    }
+
+    $session->{activated}->{$storeid} = 1;
+}
+
+sub activate_storage_list {
+    my ($cfg, $storeid_list, $session) = @_;
+
+    $session = {} if !$session;
+
+    foreach my $storeid (@$storeid_list) {
+       __activate_storage_full ($cfg, $storeid, $session);
+    }
+}
+
+sub activate_storage {
+    my ($cfg, $storeid) = @_;
+
+    my $session = {};
+
+    __activate_storage_full ($cfg, $storeid, $session);
+}
+
+sub activate_volumes {
+    my ($cfg, $vollist) = @_;
+
+    my $storagehash = {};
+    foreach my $volid (@$vollist) {
+       my ($storeid, undef) = parse_volume_id ($volid);
+       $storagehash->{$storeid} = 1;
+    }
+
+    activate_storage_list ($cfg, [keys %$storagehash]);
+
+    foreach my $volid (@$vollist) {
+       my ($storeid, $volname) = parse_volume_id ($volid);
+
+       my $scfg = storage_config ($cfg, $storeid);
+
+       my $path = path ($cfg, $volid);
+
+       if ($scfg->{type} eq 'lvm') {
+           my $cmd = ['/sbin/lvchange', '-aly', $path];
+           eval { run_command ($cmd); };
+           warn $@ if $@;
+       }
+
+       # check is volume exists
+       if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+           die "volume '$volid' does not exist\n" if ! -f $path;
+       } else {
+           die "volume '$volid' does not exist\n" if ! -b $path;
+       }
+    }
+}
+
+sub deactivate_volumes {
+    my ($cfg, $vollist) = @_;
+
+    my $lvs = lvm_lvs ();
+
+    foreach my $volid (@$vollist) {
+       my ($storeid, $volname) = parse_volume_id ($volid);
+
+       my $scfg = storage_config ($cfg, $storeid);
+
+       if ($scfg->{type} eq 'lvm') {
+           my ($name) = parse_volname_lvm ($volname);
+
+           if ($lvs->{$scfg->{vgname}}->{$name}) {
+               my $path = path ($cfg, $volid);
+               my $cmd = ['/sbin/lvchange', '-aln', $path];
+               eval { run_command ($cmd); };
+               warn $@ if $@;
+           }
+       }
+    }
+}
+
+sub deactivate_storage {
+    my ($cfg, $storeid) = @_;
+
+    my $iscsi_sessions;
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    my $type = $scfg->{type};
+
+    if ($type eq 'dir') {
+       # nothing to do
+    } elsif ($type eq 'nfs') {
+       my $mountdata = read_proc_mounts();
+       my $server = $scfg->{server};
+       my $export = $scfg->{export};
+       my $path = $scfg->{path};
+
+       my $cmd = ['/bin/umount', $path];
+
+       run_command ($cmd) if nfs_is_mounted ($server, $export, $path, $mountdata); 
+    } elsif ($type eq 'lvm') {
+       my $cmd = ['/sbin/vgchange', '-aln', $scfg->{vgname}];
+       run_command ($cmd);
+    } elsif ($type eq 'iscsi') {
+       my $portal = $scfg->{portal};
+       my $target = $scfg->{target};
+
+       my $iscsi_sessions = iscsi_session_list();
+       iscsi_logout ($target, $portal)
+           if defined ($iscsi_sessions->{$target});
+
+    } else {
+       die "implement me";
+    }
+}
+
+sub storage_info { 
+    my ($cfg, $content) = @_;
+
+    my $ids = $cfg->{ids};
+
+    my $info = {};
+    my $stypes = {};
+
+    my $slist = [];
+    foreach my $storeid (keys %$ids) {
+
+       next if $content && !$ids->{$storeid}->{content}->{$content};
+
+       next if !storage_check_enabled($cfg, $storeid, undef, 1);
+
+       my $type = $ids->{$storeid}->{type};
+
+       $info->{$storeid} = { 
+           type => $type,
+           total => 0, 
+           avail => 0, 
+           used => 0, 
+           content => content_hash_to_string($ids->{$storeid}->{content}),
+           active => 0,
+       };
+
+       $stypes->{$type} = 1;
+
+       push @$slist, $storeid;
+    }
+
+    my $session = {};
+    my $mountdata = '';
+    my $iscsi_sessions = {};
+    my $vgs = {};
+
+    if ($stypes->{lvm}) {
+       $session->{vgs} = lvm_vgs();
+       $vgs = $session->{vgs};
+    }
+    if ($stypes->{nfs}) {
+       $mountdata = read_proc_mounts();
+       $session->{mountdata} = $mountdata;
+    }
+    if ($stypes->{iscsi}) {
+       $iscsi_sessions = iscsi_session_list();
+       $session->{iscsi_sessions} = $iscsi_sessions;
+    } 
+    eval { activate_storage_list ($cfg, $slist, $session); };
+
+    foreach my $storeid (keys %$ids) {
+       my $scfg = $ids->{$storeid};
+
+       next if !$info->{$storeid};
+
+       my $type = $scfg->{type};
+
+       if ($type eq 'dir' || $type eq 'nfs') {
+
+           my $path = $scfg->{path};
+           
+           if ($type eq 'nfs') {
+               my $server = $scfg->{server};
+               my $export = $scfg->{export};
+
+               next if !nfs_is_mounted ($server, $export, $path, $mountdata); 
+           }
+
+           my $timeout = 2;
+           my $res = PVE::Tools::df($path, $timeout);
+
+           next if !$res || !$res->{total};
+
+           $info->{$storeid}->{total} = $res->{total}; 
+           $info->{$storeid}->{avail} = $res->{avail}; 
+           $info->{$storeid}->{used} = $res->{used}; 
+           $info->{$storeid}->{active} = 1;
+
+       } elsif ($type eq 'lvm') {
+
+           my $vgname = $scfg->{vgname};
+
+           my $total = 0;
+           my $free = 0;
+
+           if (defined ($vgs->{$vgname})) {
+               $total = $vgs->{$vgname}->{size};
+               $free = $vgs->{$vgname}->{free};
+
+               $info->{$storeid}->{total} = $total; 
+               $info->{$storeid}->{avail} = $free; 
+               $info->{$storeid}->{used} = $total - $free; 
+               $info->{$storeid}->{active} = 1;
+           }
+
+       } elsif ($type eq 'iscsi') {
+
+           $info->{$storeid}->{total} = 0; 
+           $info->{$storeid}->{avail} = 0; 
+           $info->{$storeid}->{used} = 0; 
+           $info->{$storeid}->{active} = 
+               defined ($iscsi_sessions->{$scfg->{target}});
+
+       } else {
+           die "implement me";
+       }
+    }
+
+    return $info;
+}
+
+sub resolv_server {
+    my ($server) = @_;
+    
+    my $packed_ip = gethostbyname($server);
+    if (defined $packed_ip) {
+       return inet_ntoa($packed_ip);
+    }
+    return undef;
+}
+
+sub scan_nfs {
+    my ($server_in) = @_;
+
+    my $server;
+    if (!($server = resolv_server ($server_in))) {
+       die "unable to resolve address for server '${server_in}'\n";
+    }
+
+    my $cmd = ['/sbin/showmount',  '--no-headers', '--exports', $server];
+
+    my $res = {};
+    run_command ($cmd, outfunc => sub {
+       my $line = shift;
+
+       # note: howto handle white spaces in export path??
+       if ($line =~ m!^(/\S+)\s+(.+)$!) {
+           $res->{$1} = $2;
+       }
+    });
+
+    return $res;
+}
+
+sub resolv_portal {
+    my ($portal, $noerr) = @_;
+
+    if ($portal =~ m/^([^:]+)(:(\d+))?$/) {
+       my $server = $1;
+       my $port = $3;
+
+       if (my $ip = resolv_server($server)) {
+           $server = $ip;
+           return $port ? "$server:$port" : $server;
+       }
+    }
+    return undef if $noerr;
+
+    raise_param_exc({ portal => "unable to resolve portal address '$portal'" });
+}
+
+# idea is from usbutils package (/usr/bin/usb-devices) script
+sub __scan_usb_device {
+    my ($res, $devpath, $parent, $level) = @_;
+
+    return if ! -d $devpath;
+    return if $level && $devpath !~ m/^.*[-.](\d+)$/;
+    my $port = $level ? int($1 - 1) : 0;
+
+    my $busnum = int(file_read_firstline("$devpath/busnum"));
+    my $devnum = int(file_read_firstline("$devpath/devnum"));
+
+    my $d = {
+       port => $port,
+       level => $level,
+       busnum => $busnum,
+       devnum => $devnum,
+       speed => file_read_firstline("$devpath/speed"),
+       class => hex(file_read_firstline("$devpath/bDeviceClass")),
+       vendid => file_read_firstline("$devpath/idVendor"),
+       prodid => file_read_firstline("$devpath/idProduct"),
+    };
+
+    if ($level) {
+       my $usbpath = $devpath;
+       $usbpath =~ s|^.*/\d+\-||;
+       $d->{usbpath} = $usbpath;
+    }
+
+    my $product = file_read_firstline("$devpath/product");
+    $d->{product} = $product if $product;
+    
+    my $manu = file_read_firstline("$devpath/manufacturer");
+    $d->{manufacturer} = $manu if $manu;
+
+    my $serial => file_read_firstline("$devpath/serial");
+    $d->{serial} = $serial if $serial;
+
+    push @$res, $d;
+
+    foreach my $subdev (<$devpath/$busnum-*>) {
+       next if $subdev !~ m|/$busnum-[0-9]+(\.[0-9]+)*$|;
+       __scan_usb_device($res, $subdev, $devnum, $level + 1);
+    }
+
+};
+
+sub scan_usb {
+
+    my $devlist = [];
+
+    foreach my $device (</sys/bus/usb/devices/usb*>) {
+       __scan_usb_device($devlist, $device, 0, 0);
+    }
+
+    return $devlist;
+}
+
+sub scan_iscsi {
+    my ($portal_in) = @_;
+
+    my $portal;
+    if (!($portal = resolv_portal ($portal_in))) {
+       die "unable to parse/resolve portal address '${portal_in}'\n";
+    }
+
+    return iscsi_discovery($portal);
+}
+
+sub storage_default_format {
+    my ($cfg, $storeid) = @_;
+
+    my $scfg = storage_config ($cfg, $storeid);
+
+    my $def = $default_config->{$scfg->{type}};
+
+    my $def_format = 'raw';
+    my $valid_formats = [ $def_format ];
+
+    if (defined ($def->{format})) {
+       $def_format = $scfg->{format} || $def->{format}->[1];
+       $valid_formats = [ sort keys %{$def->{format}->[0]} ];
+    }
+   
+    return wantarray ? ($def_format, $valid_formats) : $def_format;
+}
+
+sub vgroup_is_used {
+    my ($cfg, $vgname) = @_;
+
+    foreach my $storeid (keys %{$cfg->{ids}}) {
+       my $scfg = storage_config ($cfg, $storeid);
+       if ($scfg->{type} eq 'lvm' && $scfg->{vgname} eq $vgname) {
+           return 1;
+       }
+    }
+
+    return undef;
+}
+
+sub target_is_used {
+    my ($cfg, $target) = @_;
+
+    foreach my $storeid (keys %{$cfg->{ids}}) {
+       my $scfg = storage_config ($cfg, $storeid);
+       if ($scfg->{type} eq 'iscsi' && $scfg->{target} eq $target) {
+           return 1;
+       }
+    }
+
+    return undef;
+}
+
+sub volume_is_used {
+    my ($cfg, $volid) = @_;
+
+    foreach my $storeid (keys %{$cfg->{ids}}) {
+       my $scfg = storage_config ($cfg, $storeid);
+       if ($scfg->{base} && $scfg->{base} eq $volid) {
+           return 1;
+       }
+    }
+
+    return undef;
+}
+
+sub storage_is_used {
+    my ($cfg, $storeid) = @_;
+
+    foreach my $sid (keys %{$cfg->{ids}}) {
+       my $scfg = storage_config ($cfg, $sid);
+       next if !$scfg->{base};
+       my ($st) = parse_volume_id ($scfg->{base});
+       return 1 if $st && $st eq $storeid;
+    }
+
+    return undef;
+}
+
+sub foreach_volid {
+    my ($list, $func) = @_;
+
+    return if !$list;
+
+    foreach my $sid (keys %$list) {
+       foreach my $info (@{$list->{$sid}}) {
+           my $volid = $info->{volid};
+          my ($sid1, $volname) = parse_volume_id ($volid, 1);
+          if ($sid1 && $sid1 eq $sid) {
+              &$func ($volid, $sid, $info);
+          } else {
+              warn "detected strange volid '$volid' in volume list for '$sid'\n";
+          }
+       }
+    }
+}
+
+1;
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..89a365e
--- /dev/null
+++ b/README
@@ -0,0 +1,74 @@
+STORAGE Design:
+===============
+
+pool: ability to create more than one volume 
+
+       - directory (NFS server, local dir)
+
+       - LVM group
+
+       - physical disk (partitions) ??
+
+       - ISCSI volume pools ??
+
+       - qemu base image ??
+
+  a pool can support several formats (raw, qcow2, vmdk, ...)
+
+volume: can be used for VM storage
+
+       - block device 
+
+       - file (raw, qcow2, ...)
+
+       - ISCSI LUN
+
+A pool is either shared of local. The resulting volume
+inherits that property.
+
+
+lvs --separator , --noheadings --units b --unbuffered --nosuffix --options "lv_name,uuid,devices,seg_size,vg_extent_size"
+
+pvs --noheadings -o pv_name,vg_name 
+
+vgs --separator : --noheadings --units b --unbuffered --nosuffix --options "vg_size,vg_free" VGNAME 
+
+
+What about ISO/template storage?
+
+Storage Configuration:
+======================
+
+/etc/pve/storage.shared
+/etc/pve/storage.local
+
+oder
+
+/etc/pve/storage.config
+
+mit node attribute for jeden pool.
+
+jedes volume kann einen owner haben (VMID)??
+
+
+Aus einem pool werden volumes generiert. Jedes volume is einer VMID zugeordnet, entweder
+üder den Pfad im filesystem:
+
+$PATH/images/$VMID/xyz.qcow2
+
+oder über lvm tags: 
+
+pve-vm-$vmid
+
+Namen müssen pro storage 'unique' sein, daher werden folgende namen verwendet:
+
+vm-$VMID-disk-XXX.$EXT
+
+Nur mit einzigartigen namem kann man kurze storage-id generieren. 
+
+store1:vm-100-disk-5
+
+Configuration format:
+
+pool: <POOL_ID>
+       type <dir|vg>
\ No newline at end of file
diff --git a/changelog.Debian b/changelog.Debian
new file mode 100644 (file)
index 0000000..50d6ff0
--- /dev/null
@@ -0,0 +1,85 @@
+libpve-storage-perl (2.0-4) unstable; urgency=low
+
+  * return numeric values for channel/ID/LUN
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 05 Aug 2011 08:46:58 +0200
+
+libpve-storage-perl (2.0-3) unstable; urgency=low
+
+  * implemented node restrictions (storage can be restricted to specific
+    nodes - i.e. DRBD)
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 29 Jul 2011 08:55:11 +0200
+
+libpve-storage-perl (2.0-2) unstable; urgency=low
+
+  * backport fixes (multipath, cache) from stable
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 25 Jul 2011 07:02:06 +0200
+
+libpve-storage-perl (2.0-1) unstable; urgency=low
+
+  * change copyright to AGPL
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 19 Aug 2010 10:15:46 +0200
+
+libpve-storage-perl (1.0-10) unstable; urgency=low
+
+  * fix used space compute
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 11 Feb 2010 10:48:58 +0100
+
+libpve-storage-perl (1.0-9) unstable; urgency=low
+
+  * also query used space as suggested by Slavio
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 04 Feb 2010 08:57:02 +0100
+
+libpve-storage-perl (1.0-8) unstable; urgency=low
+
+  * also list vmdk files
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 25 Jan 2010 11:52:43 +0100
+
+libpve-storage-perl (1.0-7) unstable; urgency=low
+
+  * fix iscsi device detection on kernel 2.6.32
+
+ -- Proxmox Support Team <support@proxmox.com>  Mon, 18 Jan 2010 13:37:24 +0100
+
+libpve-storage-perl (1.0-6) unstable; urgency=low
+
+  * fix bug in parse_volume_id (ignore case)
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Oct 2009 09:22:37 +0100
+
+libpve-storage-perl (1.0-5) unstable; urgency=low
+
+  * fix bug in parse_volume_id
+
+ -- Proxmox Support Team <support@proxmox.com>  Tue, 27 Oct 2009 10:45:49 +0100
+
+libpve-storage-perl (1.0-4) unstable; urgency=low
+
+  * new functions to list backup files
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 08 Oct 2009 13:34:45 +0200
+
+libpve-storage-perl (1.0-3) unstable; urgency=low
+
+  * new install/delete template functions
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 07 Oct 2009 08:29:55 +0200
+
+libpve-storage-perl (1.0-2) unstable; urgency=low
+
+  * do not remove storage which is used as base for other storage.
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Sep 2009 08:05:32 +0200
+
+libpve-storage-perl (1.0-1) unstable; urgency=low
+
+  * initial package
+
+ -- Proxmox Support Team <support@proxmox.com>  Fri, 20 Mar 2009 11:13:19 +0100
+
diff --git a/control.in b/control.in
new file mode 100644 (file)
index 0000000..40b08df
--- /dev/null
@@ -0,0 +1,9 @@
+Package: libpve-storage-perl
+Version: @@VERSION@@-@@PKGRELEASE@@
+Section: perl
+Priority: optional
+Architecture: @@ARCH@@
+Depends: perl (>= 5.6.0-16), nfs-common, udev, libpve-common-perl
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Description: Proxmox VE storage management library
+ This package contains the storage management library used by Proxmox VE.
diff --git a/copyright b/copyright
new file mode 100644 (file)
index 0000000..f96f3fb
--- /dev/null
+++ b/copyright
@@ -0,0 +1,16 @@
+Copyright (C) 2010 Proxmox Server Solutions GmbH
+
+This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/pvesm b/pvesm
new file mode 100755 (executable)
index 0000000..dbee738
--- /dev/null
+++ b/pvesm
@@ -0,0 +1,278 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Long;
+use Fcntl ':flock';
+use File::Path;
+
+use PVE::SafeSyslog;
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::RPCEnvironment;
+use PVE::Storage;
+use PVE::API2::Storage::Config;
+use PVE::API2::Storage::Content;
+use PVE::API2::Storage::Status;
+use PVE::API2::Storage::Scan;
+use PVE::JSONSchema qw(get_standard_option);
+
+use PVE::CLIHandler;
+
+use base qw(PVE::CLIHandler);
+
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+initlog ('pvesm');
+
+die "please run as root\n" if $> != 0;
+
+PVE::INotify::inotify_init();
+
+my $rpcenv = PVE::RPCEnvironment->init('cli');
+
+$rpcenv->init_request();
+$rpcenv->set_language($ENV{LANG});
+$rpcenv->set_user('root@pam'); 
+
+__PACKAGE__->register_method ({
+    name => 'path', 
+    path => 'path',
+    method => 'GET',
+    description => "Get filesystem path for specified volume",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           volume => {
+               description => "Volume identifier",
+               type => 'string', format => 'pve-volume-id',
+           },
+       },
+    },
+    returns => { type => 'null' },
+    
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = PVE::Storage::config();
+
+       my $path = PVE::Storage::path ($cfg, $param->{volume});
+
+       print "$path\n";
+
+       return undef;
+
+    }});
+
+my $print_content = sub {
+    my ($list) = @_;
+
+    my $maxlenname = 0;
+    foreach my $info (@$list) {
+
+       my $volid = $info->{volid};
+       my $sidlen =  length ($volid);
+       $maxlenname = $sidlen if $sidlen > $maxlenname;
+    }
+
+    foreach my $info (@$list) {
+       next if !$info->{vmid};
+       my $volid = $info->{volid};
+
+       printf "%-${maxlenname}s %5s %10d %d\n", $volid, 
+       $info->{format}, $info->{size}, $info->{vmid}; 
+    }
+
+    foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
+       next if $info->{vmid};
+       my $volid = $info->{volid};
+
+       printf "%-${maxlenname}s %5s %10d\n", $volid, 
+       $info->{format}, $info->{size}; 
+    }
+};
+
+my $print_status = sub {
+    my $res = shift;
+
+    my $maxlen = 0;
+    foreach my $res (@$res) {
+       my $storeid = $res->{storage};
+       $maxlen = length ($storeid) if length ($storeid) > $maxlen;
+    }
+    $maxlen+=1;
+    
+    foreach my $res (sort { $a->{storage} cmp $b->{storage} } @$res) {
+       my $storeid = $res->{storage};
+
+       my $sum = $res->{used} + $res->{avail};
+       my $per = $sum ? (0.5 + ($res->{used}*100)/$sum) : 100;
+
+       printf "%-${maxlen}s %5s %1d %15d %15d %15d %.2f%%\n", $storeid, 
+       $res->{type}, $res->{active},
+       $res->{total}/1024, $res->{used}/1024, $res->{avail}/1024, $per; 
+    }
+};
+
+my $nodename = PVE::INotify::nodename();
+
+my $cmddef = {
+    add => [ "PVE::API2::Storage::Config", 'create', ['storage'] ],
+    set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ],
+    remove => [ "PVE::API2::Storage::Config", 'delete', ['storage'] ],
+    status => [ "PVE::API2::Storage::Status", 'index', [], 
+               { node => $nodename }, $print_status ],
+    list => [ "PVE::API2::Storage::Content", 'index', ['storage'], 
+             { node => $nodename }, $print_content ],
+    alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'], 
+              { node => $nodename }, sub {
+                  my $volid = shift;
+                  print "sucessfuly created '$volid'\n";
+              }],
+    free => [ "PVE::API2::Storage::Content", 'delete', ['volume'],
+             { node => $nodename } ],
+    nfsscan => [ "PVE::API2::Storage::Scan", 'nfsscan', ['server'],
+                { node => $nodename }, sub  {
+                    my $res = shift;
+
+                    my $maxlen = 0;
+                    foreach my $rec (@$res) {
+                        my $len = length ($rec->{path});
+                        $maxlen = $len if $len > $maxlen;
+                    }
+                    foreach my $rec (@$res) {
+                        printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
+                    }
+                }],
+    iscsiscan => [ "PVE::API2::Storage::Scan", 'iscsiscan', ['server'],
+                  { node => $nodename }, sub  {
+                      my $res = shift;
+
+                      my $maxlen = 0;
+                      foreach my $rec (@$res) {
+                          my $len = length ($rec->{target});
+                          $maxlen = $len if $len > $maxlen;
+                      }
+                      foreach my $rec (@$res) {
+                          printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
+                      }
+                  }],
+    lvmscan => [ "PVE::API2::Storage::Scan", 'lvmscan', [],
+                { node => $nodename }, sub  {
+                    my $res = shift;
+                    foreach my $rec (@$res) {
+                        printf "$rec->{vg}\n";
+                    }
+                }],
+    path => [ __PACKAGE__, 'path', ['volume']],
+};
+
+my $cmd = shift;
+
+if ($cmd && $cmd eq 'verifyapi') {
+    PVE::RESTHandler::validate_method_schemas();
+    exit 0;
+}
+
+PVE::CLIHandler::handle_cmd($cmddef, "pvesm", $cmd, \@ARGV);
+
+exit 0;
+
+__END__
+
+=head1 NAME
+
+pvesm - PVE Storage Manager
+
+=head1 SYNOPSIS
+
+    pvesm <COMMAND> [OPTIONS]
+
+    # scan iscsi host for available targets
+    pvesm scan iscsi <HOST[:PORT]>
+
+    # scan nfs server for available exports
+    pvesm scan nfs <HOST>
+
+    # add storage pools
+    pvesm add <STORAGE_ID> <TYPE> <OPTIONS> 
+    pvesm add <STORAGE_ID> dir --path <PATH> 
+    pvesm add <STORAGE_ID> nfs --path <PATH> --server <SERVER> --export <EXPORT> 
+    pvesm add <STORAGE_ID> lvm --vgname <VGNAME>
+    pvesm add <STORAGE_ID> iscsi --portal <HOST[:PORT]> --target <TARGET> 
+
+    # disable storage pools
+    pvesm set <STORAGE_ID> --disable 1
+
+    # enable storage pools
+    pvesm set <STORAGE_ID> --disable 0
+
+    # change/set storage options
+    pvesm set <STORAGE_ID> <OPTIONS>
+    pvesm set <STORAGE_ID> --shared 1
+    pvesm set local --format qcow2
+    pvesm set <STORAGE_ID> --content iso
+
+    # remove storage pools - does not delete any data
+    pvesm remove <STORAGE_ID>
+    
+    # add single devices??
+
+    # alloc volumes
+    pvesm alloc <STORAGE_ID> <VMID> <name> <size> [--format <raw|qcow2>]
+
+    # alloc 4G volume in local storage - use auto generated name
+    pvesm alloc local <VMID> '' 4G
+
+    # free volumes (warning: destroy/deletes all volume data)
+    pvesm free <VOLUME_ID>
+
+    # list storage status
+    pvesm status
+
+    # list storage contents
+    pvesm list <STORAGE_ID> [--vmid <VMID>]
+
+    # list volumes allocated by VMID
+    pvesm list <STORAGE_ID> --vmid <VMID> 
+
+    # list iso images
+    pvesm list <STORAGE_ID> --iso 
+
+    # list openvz templates
+    pvesm list <STORAGE_ID> --vztmpl
+
+    # show filesystem path for a volume
+    pvesm path <VOLUME_ID>
+
+    # import disks ??
+
+
+=head1 DESCRIPTION
+
+=head2 Storage pools
+
+Each storage pool is uniquely identified by its <STORAGE_ID>. 
+
+=head3 Storage content
+
+A storage can support several content types, for example virtual disk
+images, cdrom iso images, openvz templates or openvz root directories
+(C<images>, C<iso>, C<vztmpl>, C<rootdir>).
+
+=head2 Volumes
+
+A volume is identified by the <STORAGE_ID>, followed by a storage type
+dependent volume name, separated by colon. A valid <VOLUME_ID> looks like:
+
+ local:230/example-image.raw
+
+ local:iso/debian-501-amd64-netinst.iso
+
+ local:vztmpl/debian-5.0-joomla_1.5.9-1_i386.tar.gz
+
+ iscsi-storage:0.0.2.scsi-14f504e46494c4500494b5042546d2d646744372d31616d61 
+
+To get the filesystem path for a <VOLUME_ID> use:
+ pvesm path <VOLUME_ID>
+