Commit b209481c authored by Francesc Guasch's avatar Francesc Guasch
Browse files

wip(display): display x2go and spice screens

issue #1043
parent 6d60c52c
......@@ -1372,6 +1372,8 @@ sub _upgrade_tables {
$self->_upgrade_table('domains','needs_restart','int not null default 0');
$self->_upgrade_table('domains_network','allowed','int not null default 1');
$self->_upgrade_table('domains_kvm','xml_inactive','text');
$self->_upgrade_table('iptables','id_vm','int DEFAULT NULL');
$self->_upgrade_table('vms','security','varchar(255) default NULL');
$self->_upgrade_table('grant_types','enabled','int not null default 1');
......
......@@ -142,7 +142,6 @@ has 'description' => (
#
around 'display_info' => \&_around_display_info;
around 'display_file_tls' => \&_around_display_file_tls;
around 'add_volume' => \&_around_add_volume;
around 'remove_volume' => \&_around_remove_volume;
......@@ -1226,6 +1225,7 @@ sub _display_info_x2go($self, $user) {
my %info = %$port;
$info{ip} = $ip;
$info{display} = "x2go://$ip:".$port->{public_port};
$info{port} = delete $info{public_port};
return \%info;
}
......@@ -1249,7 +1249,7 @@ xinerama=false
clipboard=both
usekbd=true
type=auto
sshport=$info->{public_port}
sshport=$info->{port}
sound=true
soundsystem=pulse
startsoundsystem=true
......@@ -1277,8 +1277,7 @@ sub _display_file_spice($self,$user, $tls = 0) {
my $display = $self->display_info($user,'spice');
confess "I can't find ip port in ".Dumper($display)
if !$display->{ip} || !exists $display->{port} || !$display->{port};
return if !defined $display->{port};
my $ret =
"[virt-viewer]\n"
......@@ -1334,6 +1333,7 @@ sub _screen_type($self, $info=undef) {
}
for my $screen ( @{$screen} ) {
confess Dumper($screen) if !exists $screen->{driver};
return $screen->{driver};
}
$screen_type = $self->_default_screen_type if !$screen_type;
......@@ -2302,29 +2302,27 @@ sub _open_exposed_port($self, $internal_port) {
my $local_ip = $self->_vm->ip;
my $internal_ip = $self->ip;
confess "Error: I can't get the internal IP of ".$self->name
if !$internal_ip || $internal_ip !~ /^(\d+\.\d+)/;
$self->_vm->iptables(
t => 'nat'
,A => 'PREROUTING'
,p => 'tcp'
,d => $local_ip
,dport => $public_port
,j => 'DNAT'
,'to-destination' => "$internal_ip:$internal_port"
) if !$>;
if !$internal_ip || $internal_ip !~ /^(\d+\.\d+)/;
if ( !$> ) {
$self->_vm->iptables(
t => 'nat'
,A => 'PREROUTING'
,p => 'tcp'
,d => $local_ip
,dport => $public_port
,j => 'DNAT'
,'to-destination' => "$internal_ip:$internal_port"
);
if ($restricted) {
$self->_open_exposed_port_client($public_port);
if ($restricted) {
$self->_open_exposed_port_client($public_port);
}
$self->_open_iptables_state();
}
}
sub _open_exposed_port_client($self, $public_port) {
my $remote_ip = $self->remote_ip;
return if !$remote_ip;
my $local_ip = $self->_vm->ip;
sub _open_iptables_state($self) {
my $local_net = $self->ip;
$local_net =~ s{(.*)\.\d+}{$1.0/24};
......@@ -2335,6 +2333,14 @@ sub _open_exposed_port_client($self, $public_port) {
,state => 'NEW,RELATED,ESTABLISHED'
,j => 'ACCEPT'
);
}
sub _open_exposed_port_client($self, $public_port) {
my $remote_ip = $self->remote_ip;
return if !$remote_ip;
my $local_ip = $self->_vm->ip;
$self->_vm->iptables(
A => $IPTABLES_CHAIN
,s => $remote_ip
......
......@@ -119,7 +119,9 @@ sub list_disks {
my $self = shift;
my @disks = ();
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
my $xml = $self->xml_description;
confess $self->name if !$xml;
my $doc = XML::LibXML->load_xml(string => $xml);
for my $disk ($doc->findnodes('/domain/devices/disk')) {
next if $disk->getAttribute('device') ne 'disk';
......@@ -135,27 +137,45 @@ sub list_disks {
return @disks;
}
sub xml_description($self, $inactive=0) {
return $self->_data_extra('xml')
if ($self->is_removed || !$self->domain )
&& $self->is_known;
sub xml_description($self, $inactive=undef) {
if (!defined $inactive) {
if (!$self->is_active) {
$inactive = 1;
} else {
$inactive = 0;
}
}
confess if !$inactive && !$self->is_active;
my $field = "xml";
$field = "xml_inactive" if $inactive;
if (($self->is_removed || !$self->domain )
&& $self->is_known) {
my $xml = $self->_data_extra($field);
return $xml if $xml;
# try the other field if empty
if ($inactive) {
$field = "xml";
} else {
$field = "xml_inactive";
}
return $self->_data_extra($field);
}
confess "ERROR: KVM domain not available ".$self->is_known if !$self->domain;
my $xml;
eval {
my @flags;
@flags = ( Sys::Virt::Domain::XML_INACTIVE ) if $inactive;
$xml = $self->domain->get_xml_description(@flags);
$self->_data_extra('xml', $xml) if $self->is_known
&& ( $inactive
|| !$self->_data_extra('xml')
|| !$self->is_active
);
my $flags = Sys::Virt::Domain::XML_SECURE;
$flags += Sys::Virt::Domain::XML_INACTIVE if $inactive;
$xml = $self->domain->get_xml_description($flags);
$self->_data_extra($field, $xml) if $self->is_known;
};
confess $@ if $@ && $@ !~ /libvirt error code: 42/;
if ( $@ ) {
return $self->_data_extra('xml');
return $self->_data_extra($field);
}
return $xml;
}
......@@ -622,32 +642,9 @@ sub display_info($self, $user, $type='spice') {
}
sub _display_info_spice($self, $user) {
my $xml = XML::LibXML->load_xml(string => $self->xml_description);
my ($graph) = $xml->findnodes('/domain/devices/graphics')
or confess "ERROR: I can't find graphics in ".$self->name;
my ($type) = $graph->getAttribute('type');
my ($port) = $graph->getAttribute('port');
my ($tls_port) = $graph->getAttribute('tlsPort');
my ($address) = $graph->getAttribute('listen');
confess "ERROR: Machine ".$self->name." is not active in node ".$self->_vm->name."\n"
if !$port && !$self->is_active;
die "Unable to get port for domain ".$self->name." ".$graph->toString
if !$port;
my $display = $type."://$address:$port";
my %display = (
type => $type
,port => $port
,ip => $address
,display => $display
,tls_port => $tls_port
);
lock_hash(%display);
return \%display;
my @screen_spice = $self->_get_controller_screen_spice();
confess "I can't find graphics 'spice'" if !$screen_spice[0];
return $screen_spice[0]
}
=head2 is_active
......@@ -689,7 +686,7 @@ sub start {
%arg = @_;
}
my $set_password=0;
my $set_password=1;
my $remote_ip = delete $arg{remote_ip};
my $request = delete $arg{request};
......@@ -2414,5 +2411,4 @@ sub dettach($self, $user) {
$self->domain->block_pull($vol,0);
}
}
1;
......@@ -69,6 +69,9 @@ sub _set_display($self, %args){
confess $self->name."\n".Dumper($screen) if !defined $screen->{driver};
next if $screen->{driver} ne $type;
$screen->{ip} = $listen_ip;
if ($screen->{port}) {
$screen->{display} = "$type://$listen_ip:".$screen->{port};
}
$self->_store(display => $screen);
return $screen;
}
......@@ -529,11 +532,7 @@ sub _set_default_info($self, $listen_ip=undef, $screen='void') {
driver => $type
,display => "$type://".$self->_vm->ip.":".$port
};
if ($type eq 'x2go') {
$data->{public_port} = $port;
} else {
$data->{port} = $port;
}
$data->{port} = $port;
$port++;
$self->set_controller('screen',$n+1, $data);
}
......
......@@ -728,6 +728,7 @@ Returns a read-only connection to the VM.
=cut
sub open_vm {
confess "Deprecated: do not open VM from frontend";
my $self = shift;
my $type = shift or confess "I need vm type";
my $class = "Ravada::VM::$type";
......
......@@ -11,6 +11,7 @@ Ravada::Front::Domain - Frontent domain information for Ravada
use Carp qw(cluck confess croak);
use Data::Dumper;
use Hash::Util qw(lock_hash unlock_hash);
use JSON::XS;
use Moose;
......@@ -91,9 +92,10 @@ sub display_file_tls($self, $user) {
sub display_file($self,$user, $screen=$self->_screen_type) {
my $file_json = $self->_data('display_file');
confess if !$file_json;
confess "Error: no json for display_file $screen" if !$file_json;
return '' if !$file_json;
my $file = decode_json($file_json);
warn Dumper($file);
return $file->{$screen};
}
......@@ -208,4 +210,30 @@ sub _display_file_void($self, $user) {
return "port=mock\n";
}
sub _get_controller_screen_type($self, $type) {
if ($type eq 'x2go') {
my $port;
eval { $port = $self->exposed_port($type) };
return if $@ && $@ =~ /Exposed.*not found/i;
die $@ if $@;
return if !$port;
}
my $info = $self->display_info(Ravada::Utils::user_daemon, $type);
unlock_hash(%$info);
delete $info->{restricted};
delete $info->{id_domain};
delete $info->{id};
$info->{driver} = $type;
$info->{name} = $info->{driver};
$info->{file_extension} = $type;
lock_hash(%$info);
return ($info);
}
1;
......@@ -29,6 +29,7 @@ our %GET_DRIVER_SUB = (
,playback => \&_get_driver_playback
,streaming => \&_get_driver_streaming
,disk => \&_get_driver_disk
,screen => \&_get_driver_screen
);
......@@ -42,8 +43,7 @@ sub list_controllers($self) {
sub _get_controller_usb {
my $self = shift;
$self->xml_description if !$self->readonly();
my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml'));
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
my @ret;
......@@ -62,8 +62,7 @@ sub _get_controller_disk($self) {
}
sub _get_controller_network($self) {
$self->xml_description if !$self->readonly();
my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml'));
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
my @ret;
......@@ -97,65 +96,42 @@ sub _get_controller_network($self) {
}
sub _get_controller_screen($self) {
return ( $self->_get_controller_screen_spice
,$self->_get_controller_screen_x2go
return ( $self->_get_controller_screen_spice()
,$self->_get_controller_screen_type('x2go')
);
}
sub _get_controller_screen_x2go($self) {
my $port;
sub _get_controller_screen_spice($self) {
my $xml = XML::LibXML->load_xml(string => $self->xml_description);
eval { $port = $self->exposed_port('x2go') };
return if $@ && $@ =~ /Exposed.*not found/i;
die $@ if $@;
my ($graph) = $xml->findnodes('/domain/devices/graphics')
or return;
return if !$port;
my $info = $self->display_info(Ravada::Utils::user_daemon, 'x2go');
unlock_hash(%$info);
my ($type) = $graph->getAttribute('type');
my ($port) = $graph->getAttribute('port');
my ($tls_port) = $graph->getAttribute('tlsPort');
my ($address) = $graph->getAttribute('listen');
delete $info->{restricted};
delete $info->{id_domain};
delete $info->{id};
die $self->name.$graph->toString if$self->is_active && !defined $port;
$info->{driver} = 'x2go';
$info->{name} = $info->{driver};
lock_hash(%$info);
my $display;
$display = $type."://$address:$port" if defined $port;
return ($info);
}
my %display = (
driver => $type
,port => $port
,ip => $address
,display => $display
,tls_port => $tls_port
,file_extension => 'vv'
);
sub _get_controller_screen_spice($self) {
my $xml;
if (! $self->readonly ) {
$xml = $self->xml_description;
} else {
$xml = $self->_data_extra('xml');
}
my $doc = XML::LibXML->load_xml(string => $xml);
my ($password) = $graph->getAttribute('passwd');
$display{password} = $password if defined $password;
my @ret;
lock_hash(%display);
my $count = 0;
for my $graph ($doc->findnodes('/domain/devices/graphics')) {
my ($type) = $graph->getAttribute('type');
my ($address) = $graph->getAttribute('listen');
my $port = $graph->getAttribute('port');
my $tls_port = $graph->getAttribute('tlsPort');
my $display = {
type => $type
,name => $type
,port => $port
,tls_port => $tls_port
,ip => $address
,display => ''
,driver => $type
};
$display->{display} = $type."://$address:$port" if $port;
push @ret,($display);
}
return @ret;
return (\%display);
}
=head2 get_driver
......@@ -189,7 +165,7 @@ sub _get_driver_generic {
my ($tag) = $xml_path =~ m{.*/(.*)};
my @ret;
my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml'));
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
for my $driver ($doc->findnodes($xml_path)) {
my $str = $driver->toString;
......@@ -208,7 +184,7 @@ sub _get_driver_graphics {
my ($tag) = $xml_path =~ m{.*/(.*)};
my @ret;
my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml'));
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
for my $tags (qw(image jpeg zlib playback streaming)){
for my $driver ($doc->findnodes($xml_path)) {
......@@ -268,7 +244,7 @@ sub _get_driver_sound {
my $xml_path ="/domain/devices/sound";
my @ret;
my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml'));
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
for my $driver ($doc->findnodes($xml_path)) {
push @ret,('model="'.$driver->getAttribute('model').'"');
......@@ -285,6 +261,28 @@ sub _get_driver_disk {
return $volumes[0]->{driver};
}
sub _get_driver_screen($self) {
return $self->_get_driver_hardware('screen');
}
sub _get_driver_hardware($self, $hardware) {
my $info = $self->info(Ravada::Utils::user_daemon);
return $info->{hardware}->{$hardware}->[0]->{driver};
}
sub _default_screen_type { 'spice' }
sub xml_description($self, $inactive=undef) {
if (!defined $inactive) {
if (!$self->is_active) {
$inactive=1;
} else {
$inactive=0;
}
}
my $field = 'xml';
$field = "xml_inactive" if $inactive;
return $self->_data_extra($field);
}
1;
......@@ -70,7 +70,13 @@ sub _get_controller_mock($self, $name='mock') {
}
sub _get_controller_screen($self) {
return $self->_get_controller_mock('screen');
my @screen;
for my $screen ($self->_get_controller_mock('screen')) {
$screen->{file_extension} = $screen->{driver};
$screen->{file_extension} = 'vv' if $screen->{driver} =~ /spice/;
push @screen,($screen);
}
return @screen;
}
sub _get_controller_disk {
......
......@@ -437,6 +437,9 @@ sub _around_create_domain {
};
die $@ if $@ && $@ !~ /code: 55,/;
$domain->_data(spice_password => $args_create{spice_password})
if $args_create{spice_password};
$domain->expose(port => 22,name => 'x2go') if grep /^x2go$/i,@$screen;
$domain->info($owner);
$domain->display($owner) if $domain->is_active;
......@@ -1164,6 +1167,7 @@ sub _run_command_local($self, @command) {
my ($exec) = $command[0];
confess "ERROR: Missing command $exec" if ! -e $exec;
run3(\@command, \$in, \$out, \$err);
cluck("@command") if defined $command[2] && $command[1] eq '-F' && $command[2] eq 'FORWARD';
return ($out, $err);
}
......@@ -1253,25 +1257,31 @@ sub iptables($self, @args) {
}
my ($out, $err) = $self->run_command(@cmd);
warn $err if $err;
confess join(" ",@cmd)."\n$err" if $err;
warn $out if $out;
exit if $cmd[1] eq 'FORWARD';
}
sub iptables_unique($self,%rule) {
return if $self->search_iptables(%rule);
return $self->iptables(%rule);
sub iptables_unique($self,@rule) {
return if $self->search_iptables(@rule);
return $self->iptables(@rule);
}
sub search_iptables($self, %rule) {
my $table = 'filter';
my $iptables = $self->iptables_list();
LINE:for my $line (@{$iptables->{$table}}) {
for my $line (@{$iptables->{$table}}) {
my %args = @$line;
my $match = 1;
for my $key (keys %rule) {
next LINE if !exists $args{$key} || $args{$key} ne $rule{$key};
$match = 0 if !exists $args{$key} || $args{$key} ne $rule{$key};
last if !$match;
}
if ( $match ) {
return 1;
}
return 1;
}
return 0;
}
......
......@@ -724,6 +724,7 @@ sub _domain_create_from_iso {
croak "argument $_ required"
if !$args{$_};
}
confess if $args{remote_ip} && $args{remote_ip} ne '127.0.0.1' && ! $args{spice_password};
my $remove_cpu = delete $args2{remove_cpu};
for (qw(disk swap active request vm memory iso_file id_template volatile spice_password
screen
......@@ -790,14 +791,11 @@ sub _domain_create_from_iso {
$self->_xml_modify_usb($xml);
_xml_modify_video($xml);
my ($domain, $spice_password)
= $self->_domain_create_common($xml,%args);
my $domain = $self->_domain_create_common($xml,%args);
$domain->_insert_db(name=> $args{name}, id_owner => $args{id_owner}
, id_vm => $self->id
);
$domain->_set_spice_password($spice_password)
if $spice_password;
$domain->xml_description();
return $domain;
......@@ -826,8 +824,9 @@ sub _domain_create_common {
if ($remote_ip) {
my $network = Ravada::Network->new(address => $remote_ip);
$spice_password = undef if !$network->requires_password;
$listen_ip = $self->_vm->listen_ip($remote_ip);
}
$self->_xml_modify_spice_port($xml, $spice_password);
$self->_xml_modify_spice_port($xml, $spice_password, $listen_ip);
} else {
$self->_xml_remove_spice($xml);
}
......@@ -880,7 +879,7 @@ sub _domain_create_common {
, storage => $self->storage_pool
, id_owner => $id_owner
);
return ($domain, $spice_password);
return $domain;
}
sub _create_disk {
......@@ -1002,12 +1001,11 @@ sub _domain_create_from_base {
_xml_modify_disk($xml, \@device_disk);#, \@swap_disk);
my ($domain, $spice_password)