package PVE::Storage::Custom::GlusterfsPlugin;

use strict;
use warnings;
use IO::File;
use File::Path;
use PVE::Tools qw(run_command split_list);
use PVE::ProcFSTools;
use PVE::Network;
use PVE::Storage::Plugin;
use PVE::Storage::DirPlugin;
use PVE::JSONSchema qw(get_standard_option);

use base qw(PVE::Storage::Plugin);

# Glusterfs helper functions

# Bracket a bare IPv6 literal so it can be combined with ":port"/":volume" the way
# mount.glusterfs expects. DNS names and IPv4 addresses never contain a colon, so a
# colon (in an unbracketed value) unambiguously marks an IPv6 address.
sub _gluster_host {
    my ($h) = @_;
    return ($h =~ /:/ && $h !~ /^\[/) ? "[$h]" : $h;
}

sub glusterfs_is_mounted {
    my ($server, $volume, $mountpoint, $mountdata) = @_;

    $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;

    my $source = _gluster_host($server) . ":$volume";

    return $mountpoint if grep {
        $_->[2] eq 'fuse.glusterfs'
            && $_->[0] eq $source
            && $_->[1] eq $mountpoint
    } @$mountdata;
    return undef;
}

sub glusterfs_mount {
    my ($server, $backups, $volume, $mountpoint) = @_;

    my $source = _gluster_host($server) . ":$volume";

    my @opts = @$backups
        ? ('-o', 'backup-volfile-servers=' . join(':', map { _gluster_host($_) } @$backups))
        : ();

    my $cmd = ['/bin/mount', '-t', 'glusterfs', @opts, $source, $mountpoint];

    run_command($cmd, errmsg => "mount error");
}

# Configuration

sub api {
    return 13;
}

sub type {
    return 'glusterfs';
}

sub plugindata {
    return {
        content => [
            { images => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1, rootdir => 1 },
            { images => 1 },
        ],
        format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
        'sensitive-properties' => {},
    };
}

sub properties {
    return {
        volume => {
            description => "Glusterfs Volume.",
            type => 'string',
        },
        'backup-volfile-servers' => {
            description => "Backup volfile servers (list of IPs/DNS names) for client-side failover.",
            type => 'string',
            format => 'pve-storage-server-list',
            requires => 'server',
        },
        transport => {
            description => "Gluster transport: tcp or rdma",
            type => 'string',
            enum => ['tcp', 'rdma', 'unix'],
        },
    };
}

sub options {
    return {
        path => { fixed => 1 },
        server => { optional => 1 },
        'backup-volfile-servers' => { optional => 1 },
        volume => { fixed => 1 },
        transport => { optional => 1 },
        nodes => { optional => 1 },
        disable => { optional => 1 },
        'prune-backups' => { optional => 1 },
        'max-protected-backups' => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
        mkdir => { optional => 1 },
        'create-base-path' => { optional => 1 },
        'create-subdirs' => { optional => 1 },
        bwlimit => { optional => 1 },
        preallocation => { optional => 1 },
    };
}

sub check_config {
    my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;

    $config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};

    return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
}

# Storage implementation

sub status {
    my ($class, $storeid, $scfg, $cache) = @_;

    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
        if !$cache->{mountdata};

    my $path = $scfg->{path};

    my $volume = $scfg->{volume};

    my $server = $scfg->{server} // '127.0.0.1';

    return undef if !glusterfs_is_mounted($server, $volume, $path, $cache->{mountdata});

    return $class->SUPER::status($storeid, $scfg, $cache);
}

sub activate_storage {
    my ($class, $storeid, $scfg, $cache) = @_;

    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
        if !$cache->{mountdata};

    my $path = $scfg->{path};
    my $volume = $scfg->{volume};

    my $server = $scfg->{server} // '127.0.0.1';

    if (!glusterfs_is_mounted($server, $volume, $path, $cache->{mountdata})) {
        $class->config_aware_base_mkdir($scfg, $path);

        die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
            if !-d $path;

        my $backups = [split_list($scfg->{'backup-volfile-servers'} // '')];

        glusterfs_mount($server, $backups, $volume, $path);
    }

    $class->SUPER::activate_storage($storeid, $scfg, $cache);
}

sub deactivate_storage {
    my ($class, $storeid, $scfg, $cache) = @_;

    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
        if !$cache->{mountdata};

    my $path = $scfg->{path};
    my $volume = $scfg->{volume};

    my $server = $scfg->{server} // '127.0.0.1';

    if (glusterfs_is_mounted($server, $volume, $path, $cache->{mountdata})) {
        my $cmd = ['/bin/umount', $path];
        run_command($cmd, errmsg => 'umount error');
    }
}

sub activate_volume {
    my ($class, $storeid, $scfg, $volname, $snapname, $cache, $hints) = @_;

    # do nothing by default
}

sub deactivate_volume {
    my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;

    # do nothing by default
}

sub check_connection {
    my ($class, $storeid, $scfg, $cache) = @_;

    my $server = $scfg->{server} // '127.0.0.1';

    foreach my $s ($server, split_list($scfg->{'backup-volfile-servers'} // '')) {
        return 1 if PVE::Network::tcp_ping($s, 24007, 2);
    }

    return 0;
}

sub get_import_metadata {
    return PVE::Storage::DirPlugin::get_import_metadata(@_);
}

sub get_volume_notes {
    my $class = shift;
    PVE::Storage::DirPlugin::get_volume_notes($class, @_);
}

sub update_volume_notes {
    my $class = shift;
    PVE::Storage::DirPlugin::update_volume_notes($class, @_);
}

sub get_volume_attribute {
    return PVE::Storage::DirPlugin::get_volume_attribute(@_);
}

sub update_volume_attribute {
    return PVE::Storage::DirPlugin::update_volume_attribute(@_);
}

1;
