#!/usr/bin/perl -w # # Copyright 2006 VMware, Inc. All rights reserved. # # vmkfstools - Disk and VMFS Management Tool. # my @options = ( ['datacenter', '_default_'], ['createfs', 'blocksize', 'setfsname', '_default_'], ['createfs', 'setfsname', '_default_'], ['queryfs', '_default_'], ['spanfs', '_default_'], ['clonevirtualdisk', 'diskformat', '_default_'], ['createvirtualdisk', 'diskformat', '_default_'], ['createvirtualdisk', 'diskformat', 'adapterType', '_default_'], ['deletevirtualdisk', '_default_'], ['renamevirtualdisk', '_default_'], ['extendvirtualdisk', '_default_'], ['extendvirtualdisk', 'diskformat', '_default_'], ['writezeros', '_default_'], ['inflatedisk', '_default_'], ['geometry', '_default_'], ['createrdm', '_default_'], ['createrdmpassthru', '_default_'], ['queryrdm', '_default_'], ['rescanvmfs', '_default_'], ); use strict; use warnings; use Getopt::Long; use VMware::VIRuntime; use VMware::VILib; use VMware::VIExt; my %opts = ( vihost => { alias => "h", type => "=s", help => qq! The host to use when connecting via a vCenter Server. !, required => 0, }, 'datacenter' => { type => "=s", help => qq! The datacenter to use when connecting via a vCenter Server. !, required => 0, }, 'createfs' => { alias => "C", type => "=s", help => qq! Creates a VMFS file system, requires -S, and optionally -b !, required => 0, }, 'blocksize' => { alias => "b", type => "=s", help => qq! The block size of the VMFS file system to create. When omitted, the creation defaults to using 1MB for the blocksize. !, required => 0, }, 'setfsname' => { alias => "S", type => "=s", help => qq! The name of the VMFS file system to create. !, required => 0, }, 'spanfs' => { alias => "Z", type => "=s", help => qq! Extends this partition onto the head partition. !, required => 0, }, 'queryfs' => { alias => "P", type => "", help => qq! Prints information about a vmfs file system. Displays VMFS version number, the partitions constituting the vmfs file system, the capacity and availabe space. !, required => 0, }, 'adapterType' => { alias => "a", type => "=s", help => qq! The adapter type of a disk to be created. Accepts buslogic, lsilogic or ide. !, required => 0, }, 'diskformat' => { alias => "d", type => "=s", help => qq! Specify the target disk format. Applies to -c, -i, -X. Accepts zeroedthick|eagerzeroedthick|thin for -c. Accepts zeroedthick|eagerzeroedthick|thin|rdm:dev|rdmp:dev|2gbsparse for -i. Accepts eagerzeroedthick for -X. !, required => 0, }, 'clonevirtualdisk' => { alias => "i", type => "=s", help => qq! Create a copy of a virtual disk or raw disk. The copy will be in the specified disk format. Takes source disk as argument. !, required => 0, }, 'deletevirtualdisk' => { alias => "U", type => "", help => qq! Delete files associated with the specified virtual disk. !, required => 0, }, 'renamevirtualdisk' => { alias => "E", type => "=s", help => qq! Rename files associated with a specified virtual disk to the specified name. Takes source disk as argument. !, required => 0, }, 'extendvirtualdisk' => { alias => "X", type => "=s", help => qq! Takes size argument (of the form #gGmMkK) Extend the specified VMFS virtual disk to the specified length. This command is useful for extending the size of a virtual disk allocated to a virtual machine after the virtual machine has been created. However, this command requires that the guest operating system has some capability for recognizing the new size of the virtual disk and taking advantage of this new size (e.g. by updating the file system on the virtual disk to take advantage of the extra space). Since ESX 4.0 and ESXi 4.0, --diskformat can be used to specify grow the disk in eagerzeroedthick format. If the diskformat is not specified, the extended disk region of a zeroedthick disk will be zeroedthick; the extended disk region of a eagerzeroedthick disk will be eagerzeroedthick; a thin-provisioned disk will be extended as a thin-provisioned disk. !, required => 0, }, 'createrdm' => { alias => "r", type => "=s", help => qq! Creates raw disk mapping, takes the disk device path. Map a raw disk to a file on a VMFS file system. Once the mapping is established, it can be used to access the raw disk like a normal VMFS virtual disk. The 'file length' of the mapping is the same as the size of the raw disk that it points to. !, required => 0, }, 'createrdmpassthru' => { alias => "z", type => "=s", help => qq! Creates passthrough raw disk mapping, takes the disk device path. Once the mapping is established, it can be used to access the raw disk like a normal VMFS virtual disk. The 'file length' of the mapping is the same as the size of the raw disk that it points to. !, required => 0, }, 'queryrdm' => { alias => "q", type => "=s", help => qq! List the attributes of a raw disk mapping. When used with a 'rdm:' or 'raw:' specification, it prints out the vmhba name of the raw disk corresponding to the mapping referenced by the _device_. It also prints out identification information for the raw disk (if any). This option is currently not yet supported. !, required => 0, }, 'geometry' => { alias => "g", type => "", help => qq! Get the geometry information (cylinders, heads, sectors) of a virtual disk. !, required => 0, }, 'writezeros' => { alias => "w", type => "", help => qq! Initialize the virtual disk with zeros. Any existing data on virtual disk is lost. !, required => 0, }, 'inflatedisk' => { alias => "j", type => "", help => qq! Convert a `thin` virtual disk to `eagerzeroedthick` with the additional guarantee that any data on `thin` disk is preserved and any blocks that were not allocated get allocated and zeroed out. !, required => 0, }, 'createvirtualdisk' => { alias => "c", type => "=s", help => qq! Creates a virtual disk takes size argument (of the form #gGmMkK). It can be used with -a|--adapterType, -d|--diskformat. If -a is not specified, 'busLogic' will be used. If -d is not specified, 'zeroedthick' will be used. !, required => 0, }, 'rescanvmfs' => { alias => "V", type => "", help => qq! Rescan Host for new Vmfs added. !, required => 0, }, '_default_' => { type => "=s", argval => "diskpath", help => qq! The path of the target virtual disk of the operation. !, required => 0, }, ); Opts::add_options(%opts); Opts::parse(); Opts::validate(); # for file system ops my $datacenter = Opts::get_option('datacenter'); my $createfs = Opts::get_option('createfs'); my $blocksize = Opts::get_option('blocksize'); my $setfsname = Opts::get_option('setfsname'); my $queryfs = Opts::get_option('queryfs'); my $spanfs = Opts::get_option('spanfs'); my $partition = Opts::get_option('_default_'); my $path = Opts::get_option('_default_'); # for disk ops my $clonevirtualdisk = Opts::get_option('clonevirtualdisk'); my $createvirtualdisk = Opts::get_option('createvirtualdisk'); my $deletevirtualdisk = Opts::get_option('deletevirtualdisk'); my $renamevirtualdisk = Opts::get_option('renamevirtualdisk'); my $extendvirtualdisk = Opts::get_option('extendvirtualdisk'); my $geometry = Opts::get_option('geometry'); my $writezeros = Opts::get_option('writezeros'); my $inflatedisk = Opts::get_option('inflatedisk'); my $createrdm = Opts::get_option('createrdm'); my $createrdmpassthru = Opts::get_option('createrdmpassthru'); my $queryrdm = Opts::get_option('queryrdm'); my $rescanvmfs = Opts::get_option('rescanvmfs'); my $diskformat = Opts::get_option('diskformat'); my $adapterType = convert_adapter_string(Opts::get_option('adapterType')); if (defined $diskformat && $diskformat eq 'thick') { if (defined $clonevirtualdisk) { VIExt::fail("Error: Invalid destination disk format 'thick'"); } if (defined $createvirtualdisk || defined $extendvirtualdisk) { VIExt::fail("Error: Incorrect disk option 'thick'"); } } Util::connect(); my $COPYDISK_FORCE_FLAG = 1; my $vdm; my $host_view = VIExt::get_host_view(1, ['config.product.version', 'datastore', 'config.fileSystemVolume.mountInfo', 'configManager.storageSystem', 'configManager.datastoreSystem', 'config.storageDevice.scsiLun']); Opts::assert_usage(defined($host_view), "Invalid host."); my $datastoreRefs = $host_view->datastore; if (!defined $createfs && !defined $spanfs && !defined $queryfs) { $vdm = VIExt::get_virtual_disk_manager(); Opts::assert_usage(defined($vdm), "Unable to obtain disk manager."); } my $version = get_major_version($host_view); $path = convert_path($path); if (defined $queryfs) { Opts::assert_usage($path, "vmfs path is required."); query_fs($host_view, $path); } elsif (defined $createfs) { create_fs($host_view, $partition, $createfs, $setfsname, $blocksize); } elsif (defined $spanfs) { # bug 353715 extend_fs($host_view, $partition, $spanfs); } elsif (defined $clonevirtualdisk) { clone_disk($vdm, convert_path($clonevirtualdisk), $path, $diskformat, $adapterType); } elsif (defined $createvirtualdisk) { create_disk($vdm, $path, $createvirtualdisk, $diskformat, $adapterType, undef); } elsif (defined $deletevirtualdisk) { delete_disk($vdm, $path); } elsif (defined $renamevirtualdisk) { move_disk($vdm, convert_path($renamevirtualdisk), $path); } elsif (defined $extendvirtualdisk) { extend_disk($vdm, $extendvirtualdisk, $diskformat, $path); } elsif (defined $createrdm) { create_disk($vdm, $path, undef, "rdm", $adapterType, $createrdm); } elsif (defined $createrdmpassthru) { create_disk($vdm, $path, undef, "rdmp", $adapterType, $createrdmpassthru); } elsif (defined $queryrdm) { query_rdm($vdm, $path); } elsif (defined $geometry) { query_geometry($vdm, $path); } elsif (defined $writezeros) { zerofill_disk($vdm, $path); } elsif (defined $inflatedisk) { inflate_disk($vdm, $path); } elsif (defined $rescanvmfs) { rescan_vmfs($host_view); } else { Opts::usage(); exit 1; } Util::disconnect(); sub convert_path { my ($vmfs_path) = @_; # If local path is given, convert to datastore URL if (defined $vmfs_path && $vmfs_path =~ /^\/vmfs\/volumes\//) { $vmfs_path =~ s/\/$//; # remove the ending / if given my @arr = split(/\//, $vmfs_path, 5); my $dsName = $arr[3]; if (!defined $dsName) { VIExt::fail("Error: datastore is not specified."); } foreach my $dsref (@$datastoreRefs) { my $ds = Vim::get_view(mo_ref => $dsref); if ($ds->info->url =~ /$dsName/) { $dsName = $ds->info->name; last; } } $vmfs_path = (exists($arr[4]))? "[$dsName] $arr[4]" : "[$dsName]"; } return $vmfs_path; } sub get_major_version { my ($host_view) = @_; my $host_version = $host_view->{'config.product.version'}; $host_version =~ /(['e'|\d+])./; return $1; } sub get_size_kb { my $size = shift; my $size_kb = -1; if ($size =~ /^\s*(\d+)([gGmMkK])\s*$/) { my ($num, $unit) = ($1, $2); if ($unit =~ /k/i) { $size_kb = $num; } elsif ($unit =~ /m/i) { $size_kb = $num * 1024; } elsif ($unit =~ /g/i) { $size_kb = $num * 1024 * 1024; } } return $size_kb; } sub get_vmfs_version { my $vmfs_type = shift; if (defined($vmfs_type)) { if ($vmfs_type =~ /vmfs(\d+)/) { return $1; } } return 0; } sub convert_adapter_string { my $adapterType = shift; if (defined($adapterType)) { if ($adapterType =~ /^lsilogic$/i) { return "lsiLogic"; } elsif ($adapterType =~ /^buslogic$/i) { return "busLogic"; } elsif ($adapterType =~ /^ide$/i) { return "ide"; } else { # bug 468461 return $adapterType; } } else { return undef; } } sub convert_disk_format { my $disk_format = shift; if (defined($disk_format)) { if ($disk_format =~ /^2gbsparse$/i) { return "sparse2Gb"; } elsif ($disk_format =~ /^zeroedthick$/i) { return "preallocated"; } elsif ($disk_format =~ /^eagerzeroedthick$/i) { return "eagerZeroedThick"; } else { return $disk_format; } } else { return undef; } } sub create_disk { my ($vdm, $path, $size, $disk_type, $adapter_type, $device) = @_; # bug 388713, 468488 if(defined($adapter_type) && ($adapter_type eq 'ide')) { #bug 560892 if ($version ne 'e' && $version < 4) { VIExt::fail("Error: ide adapter type not supported on host."); } } $adapter_type = "busLogic" unless defined($adapter_type); $disk_type = "zeroedthick" unless defined($disk_type); my $spec; if (defined($device)) { # device-based backing $spec = new DeviceBackedVirtualDiskSpec(); if ($device !~ /^\/vmfs\/devices\/disks\//) { $device = "/vmfs/devices/disks/" . $device; } $spec->{device} = $device; } else { my $size_kb = undef; if (defined $size) { if ($size =~ /^(\d+)[gGmMkK]$/) { $size_kb = get_size_kb($size); if ($size_kb < 1024) { VIExt::fail("Error: Size too small, disk must be at least 1 MB."); } } else { VIExt::fail("Error: Invalid file length $size"); } } # file-based backing $spec = new FileBackedVirtualDiskSpec(); $spec->{capacityKb} = $size_kb; } $spec->{diskType} = convert_disk_format($disk_type); $spec->{adapterType} = $adapter_type; eval { printf ("\nAttempting to create virtual disk %s\n", $path); $vdm->CreateVirtualDisk(name => $path, spec => $spec); printf ("\nSuccessfully created virtual disk %s\n", $path); }; if ($@) { # bug 378902, 378909 if (ref($@) eq 'SoapFault') { if (defined $@->{name}) { if ($@->{name} eq 'InvalidDatastoreFault') { VIExt::fail("Error: Invalid Datastore"); } elsif ($@->{name} eq 'InvalidDatastorePathFault') { VIExt::fail("Error: Invalid Datastore Path"); } elsif (ref($@->{detail}) eq 'FileAlreadyExists') { VIExt::fail("Error: File Already Exists "); } elsif (ref($@->{detail}) eq 'RestrictedVersion') { VIExt::fail("Error: Unable to create virtual disk - RestrictedVersionFault"); } else { VIExt::fail("Unable to create virtual disk with specified parameters."); } } } else { VIExt::fail("Unable to create virtual disk : " . $@); } } } sub query_uuid { my ($vdm, $disk) = @_; my $result; eval { $result = $vdm->QueryVirtualDiskUuid(name => $disk); }; if ($@) { VIExt::fail("Unable to query uuid : " . ($@->fault_string)); } return $result; } sub set_uuid { my ($vdm, $disk, $uuid) = @_; my $result; eval { $result = $vdm->SetVirtualDiskUuid(name => $disk, uuid => $uuid); }; if ($@) { VIExt::fail("Unable to set uuid : " . ($@->fault_string)); } } # bug 384085, 407291 sub get_device_disk_type { my $type = shift; if ($type && $type =~ /(rdm:|rdmp:|raw:)(.*)$/) { my $dtype = substr($1, 0, length($1)-1); return ($dtype , $2); } else { return ($type, undef); } } sub clone_disk { my ($vdm, $src_disk, $target_disk, $disk_type, $adapter_type) = @_; my $device = undef; ($disk_type, $device) = get_device_disk_type($disk_type); $disk_type = "zeroedthick" unless defined($disk_type); $adapter_type = "busLogic" unless defined($adapter_type); my $spec = undef; if (defined($device)) { # device-based backing $spec = new DeviceBackedVirtualDiskSpec(); $spec->{device} = $device; } else { $spec = new FileBackedVirtualDiskSpec(); $spec->{capacityKb} = 10000; # dummy } # use source's format if unset $spec->{diskType} = convert_disk_format($disk_type); $spec->{adapterType} = $adapter_type; eval { printf ("\nAttempting to clone disk %s\n", $src_disk); $vdm->CopyVirtualDisk(sourceName => $src_disk, sourceDatacenter => undef, destName => $target_disk, destDatacenter => undef, destSpec => $spec, force => $COPYDISK_FORCE_FLAG); printf ("\nSuccessfully cloned disk %s to %s\n", $src_disk, $target_disk); }; if ($@) { VIExt::fail("Unable to clone virtual disk : " . ($@->fault_string)); } } sub extend_disk { my ($vdm, $size, $disk_type, $disk) = @_; my $dcRef = Vim::find_entity_view(view_type => 'Datacenter', filter => {name => $datacenter}); defined($dcRef) || die "Cannot find datacenter $datacenter"; my $eagerZero = 0; if (defined $disk_type) { if ($version ne 'e' && $version < 4) { VIExt::fail("--diskformat is supported since ESX 4.0, or ESXi 4.0\n"); } elsif ($disk_type !~ /^eagerzeroedthick$/i) { VIExt::fail("Only eagerzeroedthick diskformat is supported.\n"); } else { $eagerZero = 1; } } my $size_kb = get_size_kb($size); if ($size_kb >= 0) { eval { printf ("\nAttempting to extend disk %s\n", $disk); if ($version ne 'e' && $version < 4) { $vdm->ExtendVirtualDisk(name => $disk, datacenter => $dcRef, newCapacityKb => $size_kb); } else { $vdm->ExtendVirtualDisk(name => $disk, datacenter => $dcRef, eagerZero => $eagerZero, newCapacityKb => $size_kb); } printf ("\nSuccessfully extended disk %s\n", $disk); }; if ($@) { #VIExt::fail("Unable to extend virtual disk : " . ($@->fault_string)); print STDERR "Unable to extend virtual disk : $@\n"; } } else { VIExt::fail("$size is an invalid size. Specify [gGmMkK]"); } } sub zerofill_disk { my ($vdm, $disk) = @_; # bug 376684 eval { my $task_ref = $vdm->ZeroFillVirtualDisk_Task(name => $disk, datacenter => undef); my $task_view = Vim::get_view(mo_ref => $task_ref); print "\nProcess start\n"; track_progress($task_view); print "\nEnd process\n"; }; if ($@) { VIExt::fail("Unable to zero-fill virtual disk : " . ($@->fault_string)); } } sub inflate_disk { my ($vdm, $disk) = @_; #bug 376684 eval { my $dcRef = Vim::find_entity_view(view_type => 'Datacenter', filter => {name => $datacenter}); defined($dcRef) || die "Cannot find datacenter $datacenter"; my $task_ref = $vdm->InflateVirtualDisk_Task(name => $disk, datacenter => $dcRef); my $task_view = Vim::get_view(mo_ref => $task_ref); print "\nProcess start\n"; track_progress($task_view); print "\nEnd process\n"; }; if ($@) { #VIExt::fail("Unable to inflate virtual disk : " . ($@->fault_string)); #VIExt::fail("Unable to inflate virtual disk : " . $@; print $@ . "\n"; } } sub delete_disk { my ($vdm, $disk) = @_; eval { printf ("\nAttempting to delete disk %s\n", $disk); $vdm->DeleteVirtualDisk(name => $disk, datacenter => undef); printf ("\nSuccessfully deleted disk %s\n", $disk); }; if ($@) { VIExt::fail("Unable to delete virtual disk : " . ($@->fault_string)); } } sub move_disk { my ($vdm, $src_disk, $target_disk) = @_; my $dcRef = Vim::find_entity_view(view_type => 'Datacenter', filter => {name => 'ha-datacenter'}); eval { printf ("\nAttempting to move disk %s\n", $src_disk); $vdm->MoveVirtualDisk(sourceName => $src_disk, sourceDatacenter => $dcRef, destName => $target_disk, destDatacenter => $dcRef); printf ("\nSuccessfully moved disk %s to %s\n", $src_disk, $target_disk); }; if ($@) { VIExt::fail("Unable to move virtual disk : " . ($@->fault_string)); } } sub query_geometry { my ($vdm, $disk) = @_; my $geom; eval { $geom = $vdm->QueryVirtualDiskGeometry(name => $disk, datacenter => undef); }; if ($@) { VIExt::fail("Unable to query geometry: " . ($@->fault_string)); } my $cyl = defined($geom->cylinder) ? $geom->cylinder : "?"; my $head = defined($geom->head) ? $geom->head : "?"; my $sector = defined($geom->sector) ? $geom->sector : "?"; printf("Geometry information C/H/S is %s/%s/%s\n", $cyl, $head, $sector); } sub query_rdm { my ($vdm, $disk) = @_; VIExt::fail("This operation is not supported in this release."); } sub query_fs { my ($host, $vmfs_path) = @_; my $mounts = $host->{'config.fileSystemVolume.mountInfo'}; my @datastores = (); foreach (@$datastoreRefs) { my $datastore = Vim::get_view(mo_ref => $_); push (@datastores, $datastore); } my $found = 0; foreach (@$mounts) { my $type = ""; my $name = ""; my $UUID = ""; my $capacity = ""; my $free = "??"; my $partition = ""; my $partitions = ""; my $diskName; my $vol; my $info; my $extents; my $numExtents = 0; $info = $_->mountInfo; $vol = $_->volume; $name = $vol->{name} if $vol->{name}; # defect 224372 next unless ($info); next unless ($vmfs_path eq "/vmfs/volumes/$name" || $vmfs_path eq $info->path || $vmfs_path eq "[$name]"); $found = 1; my $info_path = $info->path; $info_path =~ s/\/vmfs\/volumes\///; $capacity = $vol->capacity; foreach (@datastores) { if ($_->info->url =~ /$info_path/) { $free = $_->info->freeSpace; } } if ($vol->isa('HostVmfsVolume')) { $type = $vol->type . "-" . $vol->version; $UUID = $vol->uuid; $extents = $vol->extent; foreach (@$extents) { $partition = $_->diskName . ":" . $_->partition; $partitions .= " "x8 . "$partition\n"; $numExtents++; } } elsif ($vol->isa('HostNasVolume')) { $type = $vol->type; $numExtents = 1; $partitions = " "x8 . "nas:$name\n"; } print "$type file system spanning $numExtents partitions.\n"; print "Capacity : $capacity, $free avail\n"; print "File system label : $name\n"; print "UUID : $UUID\n"; print "path : " . $info->path ."\n"; print "Partitions spanned:\n$partitions"; if ($info->mounted) { print "Mounted : Yes\n"; } else { print "Mounted : No\n"; } my $vaai_supported = "No"; # 4 possible values, including - Supported, Unsupported or Unknown if (defined($_->vStorageSupport) && $_->vStorageSupport =~ /Supported$/) { $vaai_supported = "Yes"; } print "VAAI Supported: " . $vaai_supported . "\n"; } if (!$found) { VIExt::fail("Could not open $partition"); } } sub extend_fs { my ($host, $head_partition, $extend_partition) = @_; # remove the prefix directory if given $head_partition =~ s/\/vmfs\/devices\/disks\///; $extend_partition =~ s/\/vmfs\/devices\/disks\///; my $ss = Vim::get_view(mo_ref => $host->{'configManager.storageSystem'}); Opts::assert_usage(defined($ss), "Unable to obtain storage system."); my $dss = Vim::get_view(mo_ref => $host->{'configManager.datastoreSystem'}); Opts::assert_usage(defined($dss), "Unable to obtain datastore system."); # find the datastore to extend my $datastore_to_extend = undef; foreach my $ds_ref (@$datastoreRefs) { last if defined($datastore_to_extend); my $datastore = Vim::get_view(mo_ref => $ds_ref); if ($datastore && $datastore->info && $datastore->info->isa('VmfsDatastoreInfo')) { my $extents = $datastore->info->vmfs->extent; foreach (@$extents) { my $p = $_->diskName . ":" . $_->partition; if ($p eq $head_partition) { $datastore_to_extend = $datastore; } } } } unless (defined $datastore_to_extend) { VIExt::fail("Unable to find datastore with head extent of " . $head_partition); return; } my $disk_name; my $partition_number; my $partition_pattern; my $partition_format; if ($version ne 'e' && $version < 4) { $partition_pattern = '(vmhba\d:\d:\d):(\d)'; $partition_format = 'vmhbaW:X:Y:Z'; } else { $partition_pattern = '([\w\W.\d\D]+):(\d+)'; $partition_format = ':'; } if ($extend_partition =~ /$partition_pattern/) { $disk_name = $1; $partition_number = $2; } else { VIExt::fail("Please supply extend partition in the form of '" . $partition_format . "'"); return; } # obtain the partition spec of the disk, use it unchanged. my $device_path = "/vmfs/devices/disks/" . $disk_name; if ($version ne 'e' && $version < 4) { $device_path = $device_path . ":0"; } else { # remove the partition part $device_path =~ s/:(\d)+//; } my @devicePaths = ($device_path); my $dpis; eval { $dpis = $ss->RetrieveDiskPartitionInfo(devicePath => \@devicePaths); }; if ($@) { VIExt::fail("Unable to retrieve disk partition info: " . ($@->fault_string)); } my $partition_spec = $dpis->[0]->spec; my $disk_part = new HostScsiDiskPartition(diskName => $disk_name, partition => $partition_number); my @disk_parts = (); push(@disk_parts, $disk_part); # get the uuid of the scsi disk to create vmfs on my $disk_uuid; my $luns = $host->{'config.storageDevice.scsiLun'}; foreach (@$luns) { if ($_->isa('HostScsiDisk')) { if ($disk_name eq $_->canonicalName) { $disk_uuid = $_->uuid; last; } } } unless (defined($disk_uuid)) { VIExt::fail("Unable to retrieve disk id."); } my $vmfs_ds_extend_spec = new VmfsDatastoreExtendSpec( diskUuid => $disk_uuid, partition => $partition_spec, extent => \@disk_parts); # bug 421500 prompt before extend my $prompt = "\nAll Data on " . $head_partition . " will be lost. Continue and format?"; my $answer = query_yes_or_no($prompt); if ($answer) { my $new_ds; eval { # bug 421500 - messages printf ("\nAttempting to extend Partition %s\n", $head_partition); $new_ds = $dss->ExtendVmfsDatastore(spec => $vmfs_ds_extend_spec, datastore => $datastore_to_extend); if (defined $new_ds) { printf ("\nSuccessfully extended Partition %s\n", $head_partition); } }; if ($@) { VIExt::fail("\nUnable to extend vmfs datastore: " . ($@->fault_string)); } } else { printf ("\nExtending and format volume not done as requested.\n"); } } sub create_fs { my ($host, $partition, $vmfs_type, $fs_name, $block_size) = @_; my $ss = Vim::get_view(mo_ref => $host->{'configManager.storageSystem'}); Opts::assert_usage(defined($ss), "Unable to obtain storage system."); Opts::assert_usage(defined($partition), "Please specify on which disk and partition to create vmfs."); if ($partition =~ /(\/vmfs\/devices\/disks\/)(\S+)/) { $partition = $2; } my $disk_name; my $partition_number; my $block_size_mb = 1; my $vmfs_version = get_vmfs_version($vmfs_type); my $partition_pattern; my $partition_format; if ($vmfs_version < 2) { VIExt::fail("Invalid vmfs type : $vmfs_type"); return; } if ($version ne 'e' && $version < 4) { $partition_pattern = '(vmhba\d:\d:\d):(\d)'; $partition_format = 'vmhbaW:X:Y:Z'; } else { $partition_pattern = '(\S+):(\d+)'; $partition_format = ':'; } if ($partition =~ /$partition_pattern/) { $disk_name = $1; $partition_number = $2; } else { VIExt::fail("Please supply partition in the form of '" . $partition_format . "'"); return; } my $disk_part = new HostScsiDiskPartition(diskName => $disk_name, partition => $partition_number); if ($block_size) { $block_size_mb = get_size_kb($block_size) / 1024; if ($block_size_mb <= 0) { $block_size_mb = 1, } } my $host_vmfs_spec = new HostVmfsSpec( extent => $disk_part, majorVersion => $vmfs_version, blockSizeMb => $block_size_mb, volumeName => $fs_name); my $vol; eval { # bug 378875 print "\nCreating " . $vmfs_type . " file system on " . $partition . " with blockSize " . ($block_size_mb * 1024 * 1024) . " and volume label " . $fs_name . "\n"; $vol = $ss->FormatVmfs(createSpec => $host_vmfs_spec); if (defined $vol) { print "\nSuccessfully created new volume:" . $vol->uuid . "\n"; } }; if ($@) { VIExt::fail("Unable to create vmfs: " . ($@->fault_string)); } } sub rescan_vmfs { my ($host) = @_; my $ss = Vim::get_view(mo_ref => $host->{'configManager.storageSystem'}); Opts::assert_usage(defined($ss), "Unable to obtain storage system."); eval { print "\nRescanning for new Vmfs on host" . "\n"; $ss->RescanVmfs(); print "\nSuccessfuly Rescanned for new Vmfs on host" . "\n"; }; if ($@) { VIExt::fail("Unable to rescan vmfs: " . ($@->fault_string)); } } #bug 376684 sub track_progress{ my ($task_view)= @_; print "0% |"; print "-" x 100; print "| 100%\n"; print " " x 4; my $progress = 0; # Keep checking the task's status until either error # or success. If the task is in progress print out # the progress. while (1) { my $info = $task_view->info; if ($info->state->val eq 'success') { # bug 274300 print "#" x (100 - $progress); print "\n"; return $info->result; } elsif ($info->state->val eq 'error') { print "\n"; my $soap_fault = SoapFault->new; $soap_fault->detail($info->error->fault); $soap_fault->fault_string($info->error->localizedMessage); die $soap_fault; } else { # Don't buffer output right here. Doing so # causes the progress bar to not display correctly. my $old_flush_value = $|; $| = 1; my $new_progress = $info->progress; if ($new_progress and $new_progress > $progress) { # Print one # for each percentage done. print "#" x ($new_progress - $progress); $progress = $new_progress; } $| = $old_flush_value; # 2 seconds between updates is fine sleep 2; $task_view->update_view_data(); } } } ### # Asks the user to choose between yes and no, and returns true or # false accordingly. Anything other than yes or no is an error. ### sub query_yes_or_no { my ($prompt_str)= @_; my $not_a_yes_or_no_str = "Choice must be yes or no."; my $prompt_yes_or_no_str = "(yes/no)? "; print "$prompt_str $prompt_yes_or_no_str"; chomp (my $input = ); $input = trim(str => $input); if ($input =~ /^yes$/i) { return 1; } elsif ($input =~ /^no$/i) { return 0; } else { die "\n$not_a_yes_or_no_str\n"; } } ### # Removes trailing and leading whitespace. ### sub trim { my %args = @_; my $str = $args{str}; Util::trace(2, "Trimming string '$str'.\n"); $str =~ s/^\s+//; $str =~ s/\s+$//; Util::trace(2, "The trimmed string is '$str'.\n"); return $str; } __END__ =head1 NAME vmkfstools - vSphere CLI for managing VMFS volumes. =head1 SYNOPSIS vmkfstools If is a file system, can be one of the following: --createfs [blocksize]kK|mM --setfsname --queryfs --extendfs If can be one of the following: --clonevirtualdisk --createdrm --createdrmpassthru --createvirtualdisk kK|mM|gG --adaptertype --diskformat --deletevirtualdisk --diskformat --extendvirtualdisk --geometry --inflatedisk --querydrm --renamevirtualdisk --writezeros --rescanvmfs =head1 DESCRIPTION You use the vmkfstools vSphere CLI to create and manipulate virtual disks, file systems, logical volumes, and physical storage devices on an ESX/ESXi host. You can use vmkfstools to create and manage a virtual machine file system (VMFS) on a physical partition of a disk and to manipulate files, such as virtual disks, stored on VMFS-3 and NFS. You can also use vmkfstools to set up and manage raw device mappings (RDMs). =head1 OPTIONS =head2 GENERAL OPTIONS =over =item B Specifies the target server and authentication information if required. Run C for a list of all connection options. =item B<--help> Prints a help message for each command-specific and each connection option. Calling the script with no arguments or with --help has the same effect. =item B<--vihost | -h Eesx_hostE> When you execute a vCLI with the C<--server> option pointing to a vCenter Server system, use C<--vihost> to specify the ESX/ESXi host to run the command against. =back =head2 FILE SYSTEM OPTIONS =over =item B<--createfs | -C vmfs3 -b | --blocksize -S | --setfsname EfsNameE EpartitionE> Creates a VMFS3 file system on a specified partition, such as naa.:1. The specified partition becomes the file system's head partition. =item B<--blocksize | -b> Specifies the block size of the VMFS file system to create. When omitted, defaults to using 1MB. =item B<--setfsname | -S> Name of the VMFS file system to create. =item B<--spanfs | -Z Espan_partitionE Ehead_partitionE> Extends the VMFS file system with the specified head partition by spanning it across the partition specified by . =item B<--queryfs | -P EdirectoryE> Lists attributes of a file or directory on a VMFS volume. Displays VMFS version number, the VMFS file system partitions, the capacity and the available space. =back =head2 VIRTUAL DISK OPTIONS =over =item B<--createvirtualdisk | -c EsizeE -a | --adaptertype EsrcfileE -d | --diskformat ElocationE> Creates a virtual disk at the specified location on a VMFS volume. With you can specify specify k|K, m|M, or g|G. Default size is 1MB, default adapter type is 'busLogic', and default disk format is 'zeroedthick'. =item B<--adapterType | -a [buslogic|lsilogic|ide]> Adapter type of a disk to be created. Accepts buslogic, lsilogic or ide. =item B<--diskformat | -d> Specifies the target disk format when used with -c, -i, or -X. For c, accepts C, C, or C. For i, accepts C, C, C, C, C, or C<2gbsparse>. For -X, accepts C. =item B<--clonevirtualdisk | -i Esrc_fileE Edest_fileE --diskformat | -d EformatE --adaptertype | -a EtypeE> Creates a copy of a virtual disk or raw disk. The copy will be in the specified disk format. Takes source disk and destination disk as arguments. =item B<--deletevirtualdisk | -U EdiskE > Deletes files associated with the specified virtual disk. =item B<--renamevirtualdisk | -E Eold_nameE Enew_nameE> Renames a specified virtual disk. =item B<--extendvirtualdisk | -X [-d eagerzeroedthick]> Extends the specified VMFS virtual disk to the specified length. This command is useful for extending the size of a virtual disk allocated to a virtual machine after the virtual machine has been created. However, this command requires that the guest operating system has some capability for recognizing the new size of the virtual disk and taking advantage of this new size (e.g. by updating the file system on the virtual disk to take advantage of the extra space). On ESX/ESXi 4.0 and later, you can use C<-d | --diskformat> to specify that the disk should grow in eagerzeroedthick format. You can use C<-d> only with eagerzeroedthick. By default, any disk, regardless of format, is extended as zeroedthick. Extending disks to eagerzeroedthick makes sense only when these virtual disks are used for fault tolerance or clustering and have to be preallocated and zeroed out up front. =item B<--createrdm | -r Erdm_fileE > Creates a raw disk mapping, that is, maps a raw disk to a file on a VMFS file system. Once the mapping is established, the mapping file can be used to access the raw disk like a normal VMFS virtual disk. The 'file length' of the mapping is the same as the size of the raw disk that it points to. =item B<--createrdmpassthru | -z EdeviceE Emap_fileE> Creates a passthrough raw disk mapping. Once the mapping is established, it can be used to access the raw disk like a normal VMFS virtual disk. The 'file length' of the mapping is the same as the size of the raw disk that it points to. =item B<--querydm | -q> This option is not currently supported. =item B<--geometry | -g> Returns the geometry information (cylinders, heads, sectors) of a virtual disk. =item B<--writezeros | -w> Initializes the virtual disk with zeros. Any existing data on the virtual disk is lost. =item B<--inflatedisk | -j> Converts a 'thin' virtual disk to 'eagerzeroedthick'. Any data on the 'thin' disk is preserved. Any blocks that were not allocated are allocated and zeroed out. =item B<--rescanvmfs | -V> Rescans the Host for any new Vmfs. =back =head1 EXAMPLES The following examples assume you are specifying connection options, either explicitly or, for example, by specifying the server, user name, and password. Run C for a list of common options including connection options. The examples use single quotes around some names; use double quotes on Windows. Create the specified file system: For ESX/ESXi version earlier than 4.0, specify the VMHBA name: vmkfstools -C vmfs3 -b 1m -S Test vmhba0:0:0:3 For ESX/ESXi version 4.0 or later, specify the device name, for example naa.xxx: vmkfstools -C vmfs3 -b 1m -S Test naa.600601604d521c002732ff0dc122dd11:3 Create a virtual disk: vmkfstools -c 2048m '[storage1] rh6.2.vmdk' Rename files associated with a specified virtual disk to the specified name: vmkfstools -E '[storage1] rh6.2.vmdk' '[storage1] testing2.vmdk' Get the geometry information (cylinders, heads, and sectors) of a virtual disk: vmkfstools -g '[storage1] testing2.vmdk' Delete an existing virtual disk: vmkfstools -U '[storage1] testing2.vmdk' Shrink the size of the virtual disk: vmkfstools -s '[storage1] rh6.2.vmdk' Extend the virtual disk to specified size, the extended region of the disk grows in eagerzeroedthick format: vmkfstools -X 1g -d eagerzeroedthick '[storage1] rh6.2.vmdk' Initialize the virtual disk with zeros: vmkfstools -w '[storage1] rh6.2.vmdk' Rescan Host for new Vmfs: vmkfstools -V =cut