[pve-devel] [PATCH v7 pve-storage 02/10] Basic FreeNAS API interaction code

mir at datanom.net mir at datanom.net
Tue Jun 20 22:39:54 CEST 2017


From: Michael Rasmussen <mir at datanom.net>

Signed-off-by: Michael Rasmussen <mir at datanom.net>
---
 PVE/Storage/FreeNASPlugin.pm | 415 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 415 insertions(+)

diff --git a/PVE/Storage/FreeNASPlugin.pm b/PVE/Storage/FreeNASPlugin.pm
index cf33e68..a50a2f6 100644
--- a/PVE/Storage/FreeNASPlugin.pm
+++ b/PVE/Storage/FreeNASPlugin.pm
@@ -15,6 +15,14 @@ use Data::Dumper;
 
 use base qw(PVE::Storage::Plugin);
 
+my $api = '/api/v1.0';
+my $api_timeout = 20; # seconds
+my $rows_per_request = 50; # limit for get requests
+                           # be aware. Setting limit very low (default setting
+                           # in FreeNAS API is 20) can cause race conditions
+                           # on the FreeNAS host (seems like an unstable
+                           # pagination algorithm implemented in FreeNAS)
+
 # Configuration
 
 sub type {
@@ -62,5 +70,412 @@ sub options {
     };
 }
 
+# private methods
+
+my $freenas_request = sub {
+    my ($scfg, $request, $section, $data) = @_;
+    my $ua = LWP::UserAgent->new;
+    $ua->agent("ProxmoxUA/0.1");
+    $ua->ssl_opts( verify_hostname => 0 );
+    $ua->timeout($api_timeout);
+    push @{ $ua->requests_redirectable }, 'POST';
+    push @{ $ua->requests_redirectable }, 'PUT';
+    push @{ $ua->requests_redirectable }, 'DELETE';
+    my ($req, $res, $content) = (undef, undef, undef);
+
+    my $url = "https://$scfg->{portal}$api/$section";
+
+    if ($request eq 'GET') {
+        $req = HTTP::Request->new;
+    } elsif ($request eq 'POST') {
+        $req = HTTP::Request->new(POST => $url);
+        $req->content($data);
+    } elsif ($request eq 'PUT') {
+        $req = HTTP::Request->new(PUT => $url);
+        $req->content($data);
+    } elsif ($request eq 'DELETE') {
+        $req = HTTP::Request->new(DELETE => $url);
+    } else {
+        die "$request: Unknown request\n";
+    }
+
+    $req->content_type('application/json');
+    $req->authorization_basic($scfg->{username}, $scfg->{password});
+    
+    if ($request eq 'GET') {
+        my $offset = 0;
+        my $keep_going = 1;
+        my $tmp;
+        $req->method('GET');
+        while ($keep_going) {
+            my $rows = 0;
+            my $uri = "$url?offset=$offset&limit=$rows_per_request";
+            $req->uri($uri);
+            $res = $ua->request($req);
+            do {
+                $keep_going = 0;
+                last;
+            } unless $res->is_success || $res->content ne "";
+            eval {
+                $tmp = decode_json($res->content);
+            };
+            do {
+                # Not JSON or invalid JSON payload
+                $tmp = $res->content;
+                if (defined $content && ref($content) eq 'ARRAY') {
+                    # error
+                    push(@$content, [$tmp]);
+                } elsif (defined $content) {
+                    $content .= $res->content;
+                } else {
+                    $content = $res->content;
+                }
+                $keep_going = 0;
+                last;
+            } if $@;
+            # We got valid JSON payload
+            if (defined $content && ref($content) eq 'ARRAY') {
+                if (ref($tmp) eq 'ARRAY') {
+                    push(@$content, @$tmp);
+                } else {
+                    # error
+                    push(@$content, [$tmp]);
+                    $keep_going = 0;
+                    last;
+                }
+            } elsif (defined $content) {
+                if (ref($tmp) eq 'ARRAY') {
+                    # error
+                    $content .= "@$tmp";
+                } else {
+                    $content .= $tmp;
+                }
+                $keep_going = 0;
+                last;
+            } else {
+                $content = $tmp;
+                if (ref($tmp) ne 'ARRAY') {
+                    $keep_going = 0;
+                    last;
+                }
+            }
+            $rows = @$tmp;
+            $keep_going = 0 unless $rows >= $rows_per_request;
+            $offset += $rows;
+        } 
+    } else {
+        $res = $ua->request($req);
+        eval {
+            $content = decode_json($res->content);
+        };
+        $content = $res->content if $@;
+    }
+
+    die $res->code."\n" unless $res->is_success;
+
+    return wantarray ? ($res->code, $content) : $content;
+};
+
+my $freenas_get_version = sub {
+    my ($scfg) = @_;
+    
+    my $response = $freenas_request->($scfg, 'GET', "system/version/");
+    my $fullversion = $response->{fullversion};
+    if ($fullversion =~ /^\w+-(\d+)\.(\d*)\.(\d*)/) {
+        my $minor = $2;
+        my $micro = $3;
+
+        if ($minor) {
+            $minor = "0$minor" unless $minor > 9;
+        } else {
+            $minor = '00';
+        }
+        
+        if ($micro) {
+            $micro = "0$micro" unless $micro > 9;
+        } else {
+            $micro = '00';
+        }
+            
+        $version = "$1$minor$micro";
+    } else {
+        die "$fullversion: Cannot parse\n";
+    }
+
+    die "$fullversion: Unsupported version\n" if $version < 90200;
+};
+
+my $freenas_list_zvol = sub {
+    my ($scfg) = @_;
+
+    $freenas_get_version->($scfg);
+    
+    my $zvols = $freenas_request->($scfg, 'GET', "storage/volume/$scfg->{pool}/zvols/");
+    my $snapshots = $freenas_request->($scfg, 'GET', "storage/snapshot/");
+
+    my $list = ();
+    my $hide = {};
+    my $vmid;
+    my $parent;
+    foreach my $zvol (@$zvols) {
+        next unless $zvol->{name} =~ /^(base|vm)-(\d+)-disk-\d+$/;
+        $vmid = $2;
+        $parent = undef;
+        foreach my $snap (@$snapshots) {
+            next unless $snap->{name} eq "__base__$vmid";
+            $parent = $snap->{filesystem} =~ /^$scfg->{pool}\/(.+)$/ ? $1 : undef;
+        }
+        $list->{$scfg->{pool}}->{$zvol->{name}} = {
+            name => $zvol->{name},
+            size => $zvol->{volsize},
+            parent => $parent,
+            vmid => $vmid,
+            format => 'raw',
+        };
+        if ($zvol->{name} =~ /^base-(.*)/) {
+            $hide->{"vm-$1"} = 1;
+        }
+    }
+
+    delete @{$list->{$scfg->{pool}}}{keys %$hide};
+    
+    return $list;
+};
+
+# Storage implementation
+
+sub volume_size_info {
+    my ($class, $scfg, $storeid, $volname, $timeout) = @_;
+
+    my (undef, $vname) = $class->parse_volname($volname);
+
+    my $zvol = $freenas_request->($scfg, 'GET', "storage/volume/$scfg->{pool}/zvols/$vname/");
+    
+    return $zvol->{volsize} if $zvol && $zvol->{volsize};
+    
+    die "Could not get zfs volume size\n";
+}
+
+sub parse_volname {
+    my ($class, $volname) = @_;
+
+    if ($volname =~ m/^(((base)-(\d+)-\S+)\/)?((base|vm)-(\d+)-\S+)$/) {
+        my $format = 'raw';
+        my $isBase = ($6 eq 'base');
+        return ('images', $5, $7, $2, $4, $isBase, $format);
+    }
+
+    die "unable to parse freenas volume name '$volname'\n";
+}
+
+sub status {
+    my ($class, $storeid, $scfg, $cache) = @_;
+
+    my $total = 0;
+    my $free = 0;
+    my $used = 0;
+    my $active = 0;
+    
+    eval {
+        my $vol = $freenas_request->($scfg, 'GET', "storage/volume/$scfg->{pool}/");
+        my $children = $vol->{children};
+        if (@$children) {
+            $used = $children->[0]{used};
+            $total = $children->[0]{avail};
+        } else {
+            $used = $vol->{used};
+            $total = $vol->{avail};
+        }
+        $free = $total - $used;
+        $active = 1;
+    };
+    warn $@ if $@;
+
+    return ($total, $free, $used, $active);
+}
+
+sub list_images {
+    my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
+
+    $cache->{freenas} = $freenas_list_zvol->($scfg) unless $cache->{freenas};
+    my $zfspool = $scfg->{pool};
+    my $res = [];
+
+    if (my $dat = $cache->{freenas}->{$zfspool}) {
+
+        foreach my $image (keys %$dat) {
+
+            my $info = $dat->{$image};
+            my $volname = $info->{name};
+            my $parent = $info->{parent};
+            my $owner = $info->{vmid};
+            
+            if ($parent) {
+                $info->{volid} = "$storeid:$parent/$volname";
+            } else {
+                $info->{volid} = "$storeid:$volname";
+            }
+            
+            if ($vollist) {
+                my $found = grep { $_ eq $info->{volid} } @$vollist;
+                next unless $found;
+            } else {
+                next if defined ($vmid) && ($owner ne $vmid);
+            }
+            push @$res, $info;
+        }
+    }
+
+    return $res;
+}
+
+sub path {
+    my ($class, $scfg, $volname, $storeid, $snapname) = @_;
+
+    my ($vtype, $vname, $vmid) = $class->parse_volname($volname);
+
+}
+
+sub create_base {
+    my ($class, $storeid, $scfg, $volname) = @_;
+    my $snap = '__base__';
+
+    my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
+        $class->parse_volname($volname);
+
+    die "create_base not possible with base image\n" if $isBase;
+
+}
+
+sub clone_image {
+    my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
+
+}
+
+sub alloc_image {
+    my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
+    die "unsupported format '$fmt'\n" if $fmt ne 'raw';
+
+}
+
+sub free_image {
+    my ($class, $storeid, $scfg, $volname, $isBase) = @_;
+
+    my ($vtype, $name, $vmid, $basename) = $class->parse_volname($volname);
+
+}
+
+sub volume_resize {
+    my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
+
+    my ($vtype, $name, $vmid) = $class->parse_volname($volname);
+
+}
+
+sub volume_snapshot {
+    my ($class, $scfg, $storeid, $volname, $snap) = @_;
+    
+    my (undef, $vname) = $class->parse_volname($volname);
+
+    my $data = {
+        dataset => "$scfg->{pool}/$vname",
+        name => $snap,
+    };
+    $freenas_request->($scfg, 'POST', "storage/snapshot/", encode_json($data));    
+}
+
+sub volume_snapshot_delete {
+    my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
+    
+    my (undef, $vname, $vmid) = $class->parse_volname($volname);
+
+}
+
+sub volume_snapshot_rollback {
+    my ($class, $scfg, $storeid, $volname, $snap) = @_;
+
+    my ($vtype, $name, $vmid) = $class->parse_volname($volname);
+}
+
+sub volume_rollback_is_possible {
+    my ($class, $scfg, $storeid, $volname, $snap) = @_; 
+    
+    my (undef, $name) = $class->parse_volname($volname);
+
+}
+
+sub volume_snapshot_list {
+    my ($class, $scfg, $storeid, $volname, $prefix) = @_;
+    # return an empty array if dataset does not exist.
+    die "Volume_snapshot_list is not implemented for FreeNAS.\n";
+}
+
+sub volume_has_feature {
+    my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
+
+    my $features = {
+        snapshot => { current => 1, snap => 1},
+        clone => { base => 1},
+        template => { current => 1},
+        copy => { base => 1, current => 1},
+    };
+
+    my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
+    $class->parse_volname($volname);
+
+    my $key = undef;
+
+    if ($snapname) {
+        $key = 'snap';
+    } else {
+        $key = $isBase ? 'base' : 'current';
+    }
+
+    return 1 if $features->{$feature}->{$key};
+
+    return undef;
+}
+
+sub activate_storage {
+    my ($class, $storeid, $scfg, $cache) = @_;
+    
+    return 1;
+}
+
+sub deactivate_storage {
+    my ($class, $storeid, $scfg, $cache) = @_;
+
+    return 1;
+}
+
+# Procedure for activating a LUN:
+#
+# if session does not exist
+#   login to target
+#   deactivate all luns in session
+# get list of active luns
+# get lun number to activate
+# make list of our luns (active + new lun)
+# rescan session
+# deactivate all luns except our luns
+sub activate_volume {
+    my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
+
+    return 1;
+}
+
+# Procedure for deactivating a LUN:
+#
+# if session exists
+#   get lun number to deactivate
+#   deactivate lun
+sub deactivate_volume {
+    my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
+
+    my (undef, $name) = $class->parse_volname($volname);
+
+   return 1;
+}
+
 1;
 
-- 
2.11.0


----

This mail was virus scanned and spam checked before delivery.
This mail is also DKIM signed. See header dkim-signature.




More information about the pve-devel mailing list