Commit 5f0a051a authored by Francesc Guasch's avatar Francesc Guasch
Browse files

Merge branch 'master' into 678_volatile_clones

parents 341832f1 0c880932
## Contributing
# Contributing
First off, thank you for considering contributing to Ravada. It's people
like you that make it such a great tool.
### 1. Where do I go from here?
## 1. Where do I go from here?
If you've noticed a bug or have a question that doesn't belong on the
[mailing list](http://groups.google.com/group/ravada)
or
[search the issue tracker](https://github.com/UPC/ravada/issues?q=something)
to see if someone else in the community has already created a ticket.
If not, go ahead and [make one](https://github.com/UPC/ravada/issues/new)!
You can also ask in our [telegram public group](https://t.me/ravadavdi).
If it is not, go ahead and [create a new issue](https://github.com/UPC/ravada/issues/new)!
### 2. Fork & create a branch
## 2. Fork & create a branch
If this is something you think you can fix, then
[fork Ravada](https://help.github.com/articles/fork-a-repo)
and create a branch with a descriptive name.
and create a branch with a descriptive name. We prepend the issue number to
the branch so it is easier to follow.
A good branch name would be (where issue #325 is the ticket you're working on):
A good branch name would be (where issue #77 is the one you're working on):
```sh
git checkout -b 325_boost_performance
git checkout -b 77_start_machine
```
If you contribute code, *thank you* ! Plase, follow this rules so our
code is in sync:
## 3. Code Style
- Use spaces, don't do tabs.
- Add the issue number at the very beggining of the commit message
``[#44] Fixed flux capacitor leak``
See our
[editor configuration](http://ravada.readthedocs.io/en/latest/devel-docs/editor-rules.html)
guidelines so your code gets along with old code. A recurrent problem for newcommers
is to submit code automatically cleaned by the editor. Usually, removed end of line
spaces or spaces converted to tabs.
Please make sure you don't do that. Run ``git diff`` before commit to see what you are
exactly contributing.
### 3. Get the tests running
## 4. Commit Format
If you contribute code, *thank you* ! Plase, follow this guide.
Each commit message consists of a header, a body, and a footer. The header has a special format that includes a type, a scope, and a description.
We use [conventional commits](https://conventionalcommits.org/) format. Each commit must be for
a reason, and we should have an [issue](https://github.com/UPC/ravada/issues) for that, so we
decided to add the issue number in the footer.
The commit message should be structured as follows:
```
type(optional scope): description
<blank line>
optional body
<blank line>
footer #issue
```
Example:
```
fix: active virtual machines can not be started
check the machine status before start
returns if machine active
before it crashed trying to start the machine
fixes #77
```
### 4.1 Header: Type
Commits must be prefixed with a type, which consists of a verb, feat, fix, build, followed by a colon and space.
Your options:
- build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm).
- ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs).
- docs: Documentation only changes.
- feat: A new feature.
- fix: A bug fix.
- perf: A code change that improves performance.
- refactor: A code change that neither fixes a bug or adds a feature.
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc).
- test: Adding missing tests or correcting existing tests.
### 4.2 Header: Optional Scope
Refers to the extent, subject matter or contextual information about your changes. A scope is a phrase describing the file modified or a section of the codebase, it is always enclosed in parenthesis.
Example for a (optional scope):
feat(parser): add ability to parse arrays
### 4.3 Header: Description
A description must immediately follow the type(optional scope): The description is a short description of the commit.
Important:
- About commit character length, keep it concise and don't write more than 50 characters.
- Use the imperative present tense: change, make, add, update, fix, etc; Do not use changed,
changes, added, fixes, fixed, etc.
- Don't capitalize the first letter.
- Do not use a dot (.) at the end.
### 4.4 Header Lenghth
The header cannot be longer than 100 characters. This allows the message to be easier to read on GitHub as well as in various git tools.
### 4.5 Writing the optional body
The body should include the motivation for the change and contrast this with previous behavior.
Example for optional body:
```
fix orthography
remove out of date paragraph
fix broken links
```
### 4.5 Writing the optional footer
The <optional footer> should contain a closing reference to an issue if any.
For example, to close an issue numbered 123, you could use the phrases Closes #123 in your
pull request description or commit message. Once the branch is merged into the default branch,
the issue will close.
## 5. Get the tests running
See this documentation about [testing](http://ravada.readthedocs.io/en/latest/devel-docs/commit-rules.html#testing) the project.
#### 4. Did you find a bug?
## 6. Did you find a bug?
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/UPC/ravada/issues).
......@@ -43,7 +139,7 @@ See this documentation about [testing](http://ravada.readthedocs.io/en/latest/de
Be sure to include a **title and clear description**, as much relevant information as possible,
and a **code sample**, an **executable test case** or a step by step guide demonstrating the expected behavior that is not occurring.
### 5. Implement your fix or feature
## 7. Implement your fix or feature
At this point, you're ready to make your changes! Feel free to ask for help;
everyone is a beginner at first :smile_cat:
......@@ -52,9 +148,20 @@ Follow this guide about running [Ravada in development mode](http://ravada.readt
If you change a translation or language file make sure you follow this small [guide](http://ravada.readthedocs.io/en/latest/devel-docs/translations.html?highlight=translate) and don't forget to add the issue number when committing.
### 6. Make a Pull Request
## 8. Push your changes
Pushing refers to sending your committed changes to a remote repository, such as a repository
hosted on GitHub. Before that all the changes where local in the computer you are working in.
After working on your changes you need to Push it (upload) your newly created branch to GitHub
git push
## 9. Create a Pull Request
At this point, you should switch back to your master branch and make sure it's
Pull requests or PR are proposed changes to a repository submitted by a user and accepted or rejected by a repository's collaborators.
When your changes are done, you should switch back to your master branch and make sure it's
up to date with Ravada's master branch:
```sh
......@@ -71,11 +178,18 @@ git rebase master
git push --set-upstream origin 325_boost_performance
```
Finally, go to GitHub and
[make a Pull Request](https://help.github.com/articles/creating-a-pull-request)
:D
Finally, go to our GitHub repository and
[create a Pull Request](https://github.com/UPC/ravada/pulls)
### 9.1 How to Write a Title for a Pull Request
Pull Request should be named in reference to the main fix or feature you provide; minor information can be added to the description. Please be specific and don't use generic terms.
Keep it concise and don't write more than 50 characters in the title.
Read [more information about PR](https://help.github.com/articles/creating-a-pull-request)
### 7. Keeping your Pull Request updated
### 9.2 Keeping your Pull Request updated
If a maintainer asks you to "rebase" your PR, they're saying that a lot of code
has changed, and that you need to update your branch so it's easier to merge.
......@@ -91,7 +205,7 @@ git pull --rebase origin master
git push --force-with-lease origin 325_boost_performance
```
### 8. Merging a PR (maintainers only)
### 9.3 Merging a PR (maintainers only)
A PR can only be merged into master by a maintainer if:
......
......@@ -6,6 +6,8 @@
[![Follow twitter](https://img.shields.io/twitter/follow/ravada_vdi.svg?style=social&label=Twitter&style=flat-square)](https://twitter.com/ravada_vdi)
[![Telegram Group](https://img.shields.io/badge/Telegram-Group-blue.svg)](https://t.me/ravadavdi)
[![Project Status: Active - The project has reached a stable, usable state and is being actively developed.](http://www.repostatus.org/badges/latest/active.svg)](http://www.repostatus.org/#active)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
## Remote Virtual Desktops Manager
......
......@@ -13,11 +13,14 @@ use Ravada;
use Ravada::Auth::SQL;
use Ravada::Auth::LDAP;
$|=1;
my $help;
my ($DEBUG, $ADD_USER );
my $VERBOSE = $ENV{TERM};
my $FILE_CONFIG_DEFAULT = "/etc/ravada.conf";
my $FILE_CONFIG;
......@@ -34,6 +37,7 @@ my $TEST_LDAP;
my $URL_ISOS;
my $ALL;
my $HIBERNATED;
my $DISCONNECTED;
my $LIST;
......@@ -70,6 +74,7 @@ my $USAGE = "$0 "
." --all : execute on all virtual machines\n"
." For hibernate, it is executed on all the actives\n"
." --hibernated: execute on hibernated machines\n"
." --disconnected: execute on disconnected machines\n"
."\n"
;
......@@ -88,6 +93,7 @@ GetOptions ( help => \$help
,'url-isos=s'=> \$URL_ISOS
,'shutdown:s'=> \$SHUTDOWN_DOMAIN
,'hibernate:s'=> \$HIBERNATE_DOMAIN
,'disconnected'=> \$DISCONNECTED
,'make-admin=s' => \$MAKE_ADMIN_USER
,'remove-admin=s' => \$REMOVE_ADMIN_USER
,'change-password'=> \$CHANGE_PASSWORD
......@@ -113,11 +119,12 @@ die "Only root can do that\n" if $> && ( $ADD_USER || $ADD_USER_LDAP || $IMPORT_
die "ERROR: Missing file config $FILE_CONFIG\n"
if $FILE_CONFIG && ! -e $FILE_CONFIG;
die "ERROR: Shutdown requires a domain name, or --all or --hibernated\n"
if defined $SHUTDOWN_DOMAIN && !$SHUTDOWN_DOMAIN && !$ALL && !$HIBERNATED;
die "ERROR: Shutdown requires a domain name, or --all , --hibernated , --disconnected\n"
if defined $SHUTDOWN_DOMAIN && !$SHUTDOWN_DOMAIN && !$ALL && !$HIBERNATED
&& !$DISCONNECTED;
die "ERROR: Hibernate requires a domain name, or --all\n"
if defined $HIBERNATE_DOMAIN && !$HIBERNATE_DOMAIN && !$ALL;
die "ERROR: Hibernate requires a domain name, or --all , --disconnected\n"
if defined $HIBERNATE_DOMAIN && !$HIBERNATE_DOMAIN && !$ALL && !$DISCONNECTED;
my %CONFIG;
%CONFIG = ( config => $FILE_CONFIG ) if $FILE_CONFIG;
......@@ -348,6 +355,12 @@ sub list {
print $domain->name."\t";
if ($domain->is_active) {
print "active";
my $status = $domain->client_status;
if ( $domain->remote_ip ) {
$status .= " , " if $status;
$status .= $domain->remote_ip;
}
print " ( $status ) " if $status;
} elsif ($domain->is_hibernated) {
print "hibernated";
} else {
......@@ -368,15 +381,22 @@ sub hibernate {
my $found = 0;
for my $domain ($rvd_back->list_domains) {
if ( ($all && $domain->is_active)
|| ($domain_name && $domain->name eq $domain_name)) {
|| ($domain_name && $domain->name eq $domain_name)
|| ($DISCONNECTED && $domain->client_status
&& $domain->client_status eq 'disconnected')
) {
$found++;
if (!$domain->is_active) {
warn "WARNING: Virtual machine ".$domain->name
." is already down.\n";
next;
}
if ( $DISCONNECTED && $domain->client_status
&& $domain->client_status eq 'disconnected') {
next if _verify_connection($domain);
}
if ($domain->can_hibernate) {
$domain->hibernate();
$domain->hibernate( $Ravada::USER_DAEMON);
$down++;
} else {
warn "WARNING: Virtual machine ".$domain->name
......@@ -388,7 +408,7 @@ sub hibernate {
}
print "$down machines hibernated.\n";
warn "ERROR: Domain $domain_name not found.\n"
if !$all && !$found;
if !$domain_name && !$found;
}
sub start_domain {
......@@ -427,9 +447,12 @@ sub shutdown_domain {
my $down = 0;
my $found = 0;
DOMAIN:
for my $domain ($rvd_back->list_domains) {
if ((defined $domain_name && $domain->name eq $domain_name)
|| ($hibernated && $domain->is_hibernated)
|| ($DISCONNECTED
&& ( $domain->client_status && $domain->client_status eq 'disconnected' ))
|| $all ){
$found++;
if (!$domain->is_active && !$domain->is_hibernated) {
......@@ -441,8 +464,14 @@ sub shutdown_domain {
if ($domain->is_hibernated) {
$domain->start(user => $Ravada::USER_DAEMON);
}
$domain->shutdown(user => $Ravada::USER_DAEMON, timeout => 60);
if ($DISCONNECTED && $domain->client_status
&& $domain->client_status eq 'disconnected') {
next DOMAIN if _verify_connection($domain);
}
print "Shutting down ".$domain->name.".\n";
eval { $domain->shutdown(user => $Ravada::USER_DAEMON, timeout => 300) };
warn $@ if $@;
$down++;
}
}
......@@ -451,6 +480,25 @@ sub shutdown_domain {
print "$down domains shut down.\n";
}
sub _verify_connection {
my $domain = shift;
print "Verifying connection for ".$domain->name
." ".($domain->remote_ip or '')." "
if $VERBOSE;
for ( 1 .. 25 ) {
if ( $domain->client_status(1)
&& $domain->client_status(1) ne 'disconnected' ) {
print "\n\t".$domain->client_status." ".$domain->remote_ip
." Shutdown dismissed.\n";
return 1;
}
print "." if $VERBOSE && !($_ % 5);
sleep 1;
}
print "\n" if $VERBOSE;
return 0;
}
sub test_ldap {
my $rvd_back = Ravada->new(%CONFIG);
eval { Ravada::Auth::LDAP::_init_ldap_admin() };
......
......@@ -132,8 +132,8 @@ sub BUILD {
$self->_create_tables();
$self->_upgrade_tables();
$self->_init_user_daemon();
$self->_update_data();
$self->_init_user_daemon();
}
sub _init_user_daemon {
......@@ -161,6 +161,7 @@ sub _update_user_grants {
my $user = Ravada::Auth::SQL->search_by_id($id);
next if $user->name() eq $USER_DAEMON_NAME;
next if $user->grants();
$USER_DAEMON->grant_user_permissions($user);
$USER_DAEMON->grant_admin_permissions($user) if $user->is_admin;
}
......@@ -650,10 +651,75 @@ sub _update_data {
$self->_remove_old_isos();
$self->_update_isos();
$self->_update_grants();
$self->_enable_grants();
$self->_update_user_grants();
$self->_update_domain_drivers_types();
$self->_update_domain_drivers_options();
$self->_update_old_qemus();
}
sub _update_grants($self) {
my $sth = $CONNECTOR->dbh->prepare(
"UPDATE grant_types"
." SET name='create_machine' "
." WHERE name = 'create_domain'"
);
$sth->execute();
}
sub _null_grants($self) {
my $sth = $CONNECTOR->dbh->prepare("SELECT count(*) FROM grant_types "
." WHERE enabled = NULL "
);
$sth->execute;
my ($count) = $sth->fetchrow;
exit if !$count && $self->{_null}++;
return $count;
}
sub _enable_grants($self) {
return if $self->_null_grants();
my $sth = $CONNECTOR->dbh->prepare(
"UPDATE grant_types set enabled=0"
);
$sth->execute;
my @grants = (
'change_settings', 'change_settings_all', 'change_settings_clones'
,'clone', 'clone_all', 'create_base', 'create_machine'
,'grant'
,'manage_users'
,'remove', 'remove_all', 'remove_clone', 'remove_clone_all'
,'shutdown_all', 'shutdown_clone'
);
$sth = $CONNECTOR->dbh->prepare("SELECT id,name FROM grant_types");
$sth->execute;
my %grant_exists;
while (my ($id, $name) = $sth->fetchrow ) {
$grant_exists{$name} = $id;
}
$sth = $CONNECTOR->dbh->prepare(
"UPDATE grant_types set enabled=1 WHERE name=?"
);
my %done;
for my $name ( sort @grants ) {
die "Duplicate grant $name " if $done{$name};
die "Permission $name doesn't exist at table grant_types"
."\n".Dumper(\%grant_exists)
if !$grant_exists{$name};
$sth->execute($name);
}
}
sub _update_old_qemus($self) {
......@@ -825,8 +891,13 @@ sub _upgrade_tables {
$self->_upgrade_table('domains','id_vm','int default null');
$self->_upgrade_table('domains','volatile_clones','int NOT NULL default 0');
$self->_upgrade_table('domains','client_status','varchar(32)');
$self->_upgrade_table('domains','client_status_time_checked','int NOT NULL default 0');
$self->_upgrade_table('domains_network','allowed','int not null default 1');
$self->_upgrade_table('grant_types','enabled','int not null default 1');
}
......@@ -2180,8 +2251,8 @@ sub _refresh_down_domains($self, $active_domain, $active_vm) {
$sth->execute();
while ( my ($id_domain, $name, $id_vm) = $sth->fetchrow ) {
next if exists $active_domain->{$id_domain};
my $domain = Ravada::Domain->open($id_domain);
if (defined $id_vm && !$active_vm->{$id_vm}) {
my $domain = Ravada::Domain->open($id_domain) or next;
if (defined $id_vm && !$active_vm->{$id_vm} ) {
$domain->_set_data(status => 'shutdown');
} else {
my $status = 'shutdown';
......@@ -2279,6 +2350,7 @@ sub search_vm {
die $@ if $@;
for my $vm (@vms) {
$vm->connect if !$vm->vm;
return $vm if ref($vm) eq $class;
}
return;
......@@ -2344,6 +2416,7 @@ sub _enforce_limits_active {
my %domains;
for my $domain ($self->list_domains( active => 1 )) {
$domain->client_status();
push @{$domains{$domain->id_owner}},$domain;
}
for my $id_user(keys %domains) {
......
......@@ -345,9 +345,10 @@ sub is_operator {
my $self = shift;
return $self->is_admin()
|| $self->can_shutdown_clone()
|| $self->can_hibernate_clone
# || $self->can_hibernate_clone()
|| $self->can_change_settings_clones()
|| $self->can_remove_clone()
|| $self->can_remove_clone_all()
|| $self->can_create_base()
|| $self->can_create_machine();
}
......@@ -362,11 +363,32 @@ sub can_list_own_machines {
my $self = shift;
return 1
if $self->can_create_base()
|| $self->can_create_machine
|| $self->can_create_machine()
|| $self->can_remove_clone_all()
;
return 0;
}
=head2 can_list_clones
Returns true if the user can list all machines that are clones and its bases
=cut
sub can_list_clones {
my $self = shift;
return 1 if $self->is_admin()
|| $self->can_remove_clone_all();
return 0;
}
sub can_list_clones_from_own_base($self) {
return 1 if $self->can_remove_clone || $self->can_remove_clone_all;
return 0;
}
=head2 can_list_machines
Returns true if the user can list all the virtual machines at the web frontend
......@@ -375,7 +397,7 @@ Returns true if the user can list all the virtual machines at the web frontend
sub can_list_machines {
my $self = shift;
return 1 if $self->is_admin();
return 1 if $self->is_admin() || $self->can_remove_all;
return 0;
}
......@@ -537,18 +559,22 @@ sub can_do($self, $grant) {
}
sub _load_grants($self) {
my $sth = $$CON->dbh->prepare(
"SELECT gt.name, gu.allowed"
my $sth;
eval { $sth= $$CON->dbh->prepare(
"SELECT gt.name, gu.allowed, gt.enabled"
." FROM grant_types gt LEFT JOIN grants_user gu "
." ON gt.id = gu.id_grant "
." AND gu.id_user=?"
);
$sth->execute($self->id);
my ($name, $allowed);
$sth->bind_columns(\($name, $allowed));
};
confess $@ if $@;
my ($name, $allowed, $enabled);
$sth->bind_columns(\($name, $allowed, $enabled));
while ($sth->fetch) {
$self->{_grant}->{$name} = $allowed;# or undef);
$self->{_grant}->{$name} = $allowed if $enabled;
$self->{_grant_disabled}->{$name} = !$enabled;
}
$sth->finish;
}
......@@ -563,7 +589,7 @@ sub grant_user_permissions($self,$user) {
$self->grant($user, 'clone');
$self->grant($user, 'change_settings');
$self->grant($user, 'remove');
$self->grant($user, 'screenshot');
# $self->grant($user, 'screenshot');
}
=head2 grant_operator_permissions
......@@ -597,6 +623,7 @@ Grant an user all the permissions
sub grant_admin_permissions($self,$user) {
my $sth = $$CON->dbh->prepare(
"SELECT name FROM grant_types "
." WHERE enabled=1"
);
$sth->execute();
while ( my ($name) = $sth->fetchrow) {
......@@ -637,6 +664,10 @@ Grant an user a specific permission, or revoke it
=cut
sub grant($self,$user,$permission,$value=1) {
confess "ERROR: permission '$permission' disabled "
if $self->{_grant_disabled}->{$permission};
if ( !$self->can_grant() && $self->name ne $Ravada::USER_DAEMON_NAME ) {
my @perms = $self->list_permissions();
confess "ERROR: ".$self->name." can't grant permissions for ".$user->name."\n"
......@@ -696,7 +727,9 @@ sub list_all_permissions($self) {
return if !$self->is_admin;
my $sth = $$CON->dbh->prepare(
"SELECT * FROM grant_types ORDER BY name"
"SELECT * FROM grant_types"
." WHERE enabled=1 "
." ORDER BY name "
);
$sth->execute;
my @list;
......@@ -722,8 +755,72 @@ sub list_permissions($self) {
return @list;
}
sub AUTOLOAD {
my $self = shift;