Author: eelco
Date: Thu May 12 15:30:01 2011
New Revision: 27237
URL: https://svn.nixos.org/websvn/nix/?rev=27237&sc=1
Log:
* Support deployment to EC2 clouds.
Added:
cloud/trunk/examples/ec2.nix
Modified:
cloud/trunk/default.nix
cloud/trunk/src/eval-machine-info.nix
cloud/trunk/src/nixos-deploy-network.pl
Modified: cloud/trunk/default.nix
==============================================================================
--- cloud/trunk/default.nix Thu May 12 11:03:29 2011 (r27236)
+++ cloud/trunk/default.nix Thu May 12 15:30:01 2011 (r27237)
@@ -8,6 +8,7 @@
buildInputs =
[ perl makeWrapper perlPackages.XMLLibXML perlPackages.JSON
perlPackages.TextTable perlPackages.ListMoreUtils
+ perlPackages.NetAmazonEC2
];
installPhase =
Added: cloud/trunk/examples/ec2.nix
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ cloud/trunk/examples/ec2.nix Thu May 12 15:30:01 2011 (r27237)
@@ -0,0 +1,18 @@
+{
+
+ webserver =
+ { config, pkgs, ... }:
+
+ {
+ services.httpd.enable = true;
+ services.httpd.adminAddr = "[email protected]";
+ services.httpd.documentRoot = "${pkgs.valgrind}/share/doc/valgrind/html";
+
+ deployment.targetEnv = "ec2";
+ deployment.ec2.controller = https://ec2.eu-west-1.amazonaws.com:443/;
+ deployment.ec2.ami = "ami-ecb49e98";
+ deployment.ec2.instanceType = "m1.large";
+ deployment.ec2.keyPair = "gsg-keypair";
+ };
+
+}
Modified: cloud/trunk/src/eval-machine-info.nix
==============================================================================
--- cloud/trunk/src/eval-machine-info.nix Thu May 12 11:03:29 2011
(r27236)
+++ cloud/trunk/src/eval-machine-info.nix Thu May 12 15:30:01 2011
(r27237)
@@ -42,6 +42,7 @@
flip mapAttrs nodes (n: v:
{ inherit (v.config.deployment) targetEnv targetHost;
adhoc = optionalAttrs (v.config.deployment.targetEnv == "adhoc")
v.config.deployment.adhoc;
+ ec2 = optionalAttrs (v.config.deployment.targetEnv == "ec2")
v.config.deployment.ec2;
}
);
Modified: cloud/trunk/src/nixos-deploy-network.pl
==============================================================================
--- cloud/trunk/src/nixos-deploy-network.pl Thu May 12 11:03:29 2011
(r27236)
+++ cloud/trunk/src/nixos-deploy-network.pl Thu May 12 15:30:01 2011
(r27237)
@@ -9,6 +9,7 @@
use Getopt::Long qw(:config posix_default gnu_getopt no_ignore_case
auto_version);
use Text::Table;
use List::MoreUtils qw(uniq);
+use Net::Amazon::EC2;
$main::VERSION = "0.1";
@@ -33,7 +34,7 @@
# address of machine ‘foo’.
my $state;
-my $stateFile = "./state.json";
+my $stateFile;
my $myDir = dirname(Cwd::abs_path($0));
@@ -83,7 +84,7 @@
, $status
, $m->{targetEnv} || $r->{targetEnv}
, $r->{vmId}
- , $r->{ipv6}
+ , $r->{ipv6} || $r->{ipv4}
];
}
@@ -92,7 +93,7 @@
{ title => "Status", align => "left" }, \ " | ",
{ title => "Type", align => "left" }, \ " | ",
{ title => "VM Id", align => "left" }, \ " | ",
- { title => "IPv6", align => "left" },
+ { title => "IP address", align => "left" },
);
$table->load(@lines);
@@ -162,7 +163,7 @@
sub evalMachineInfo {
my $machineInfoXML =
- `nix-instantiate --eval-only --xml --strict
$myDir/eval-machine-info.nix --arg networkExprs '[ @networkExprs ]' -A
machineInfo`;
+ `nix-instantiate --eval-only --xml --strict --show-trace
$myDir/eval-machine-info.nix --arg networkExprs '[ @networkExprs ]' -A
machineInfo`;
die "evaluation of @networkExprs failed" unless $? == 0;
#print $machineInfoXML, "\n";
@@ -186,6 +187,13 @@
, destroyVMCommand => $m->findvalue('./attrs/attr[@name =
"adhoc"]/attrs/attr[@name = "destroyVMCommand"]/string/@value') || die
, queryVMCommand => $m->findvalue('./attrs/attr[@name =
"adhoc"]/attrs/attr[@name = "queryVMCommand"]/string/@value') || die
};
+ } elsif ($targetEnv eq "ec2") {
+ $info->{ec2} =
+ { controller => $m->findvalue('./attrs/attr[@name =
"ec2"]/attrs/attr[@name = "controller"]/string/@value') || die
+ , ami => $m->findvalue('./attrs/attr[@name =
"ec2"]/attrs/attr[@name = "ami"]/string/@value') || die
+ , instanceType => $m->findvalue('./attrs/attr[@name =
"ec2"]/attrs/attr[@name = "instanceType"]/string/@value') || die
+ , keyPair => $m->findvalue('./attrs/attr[@name =
"ec2"]/attrs/attr[@name = "keyPair"]/string/@value') || die
+ };
} else {
die "machine ‘$name’ has an unknown target environment type
‘$targetEnv’";
}
@@ -196,7 +204,7 @@
sub readState {
local $/;
- if (-e $stateFile) {
+ if (defined $stateFile && -e $stateFile) {
open(my $fh, '<', $stateFile) or die "$!";
$state = decode_json <$fh>;
} else {
@@ -206,6 +214,7 @@
sub writeState {
+ die "state file not set; please use ‘--state’\n" unless defined $stateFile;
open(my $fh, '>', "$stateFile.new") or die "$!";
print $fh encode_json($state);
close $fh;
@@ -213,6 +222,18 @@
}
+sub openEC2 {
+ my ($name, $machine) = @_;
+ return Net::Amazon::EC2->new
+ ( AWSAccessKeyId => ($ENV{'AWS_ACCESS_KEY_ID'} || die "please set
\$AWS_ACCESS_KEY_ID\n")
+ , SecretAccessKey => ($ENV{'AWS_SECRET_ACCESS_KEY'} || die "please set
\$AWS_SECRET_ACCESS_KEY\n")
+ , # !!! This assumes that all machines have the same controller/zone.
+ base_url => $machine->{ec2}->{controller}
+ #, debug => 1
+ );
+}
+
+
sub killMachine {
my ($name, $machine) = @_;
@@ -227,7 +248,16 @@
elsif ($machine->{targetEnv} eq "adhoc") {
print STDERR "killing VM ‘$name’...\n";
system "ssh $machine->{adhoc}->{controller}
$machine->{adhoc}->{destroyVMCommand} $machine->{vmId}";
- warn "unable to kill VM: $?" unless $? == 0;
+ die "unable to kill VM: $?" unless $? == 0;
+ }
+
+ elsif ($machine->{targetEnv} eq "ec2") {
+ print STDERR "killing VM ‘$name’ (EC2 instance
‘$machine->{vmId}’)...\n";
+ my $ec2 = openEC2($name, $machine);
+ my $res = $ec2->terminate_instances(InstanceId => $machine->{vmId});
+ die "could not terminate EC2 instance: “" .
@{$res->errors}[0]->message . "”\n"
+ unless ref $res eq "ARRAY";
+ # !!! Check the state change? Wait until the machine has shut down?
}
else {
@@ -280,8 +310,8 @@
}
elsif ($machine->{targetEnv} eq "adhoc") {
-
print STDERR "starting missing VM ‘$name’...\n";
+
my $vmId = `ssh $machine->{adhoc}->{controller}
$machine->{adhoc}->{createVMCommand} $ENV{USER}-$name`;
die "unable to start VM: $?" unless $? == 0;
chomp $vmId;
@@ -307,7 +337,74 @@
system "ssh -o StrictHostKeyChecking=no root\@$ipv6 true <
/dev/null 2> /dev/null";
die "cannot SSH to VM: $?" unless $? == 0;
+ }
+
+ elsif ($machine->{targetEnv} eq "ec2") {
+ print STDERR "starting missing VM ‘$name’ on EC2 cloud
‘$machine->{ec2}->{controller}’...\n";
+
+ my $ec2 = openEC2($name, $machine);
+ my $reservation = $ec2->run_instances
+ ( ImageId => $machine->{ec2}->{ami}
+ , InstanceType => $machine->{ec2}->{instanceType}
+ , KeyName => $machine->{ec2}->{keyPair}
+ , MinCount => 1
+ , MaxCount => 1
+ );
+
+ die "could not create EC2 instance: “" .
@{$reservation->errors}[0]->message . "”\n"
+ if $reservation->isa('Net::Amazon::EC2::Errors');
+
+ my $instance = @{$reservation->instances_set}[0];
+ my $vmId = $instance->instance_id;
+
+ print STDERR
+ "got reservation ‘", $reservation->reservation_id,
+ "’, instance ‘$vmId’\n";
+
+ # !!! We should already update the state record to
+ # remember that we started an instance.
+
+ # Wait until the machine has an IP address. (It may not
+ # have finished booting, but later down we wait for the
+ # SSH port to open.)
+ print STDERR "waiting for IP address... ";
+ my $n = 0;
+ while (1) {
+ my $state = $instance->instance_state->name;
+ print STDERR "[$state] ";
+ die "EC2 instance ‘$vmId’ didn't start; it went to state
‘$state’\n"
+ if $state ne "pending" && $state ne "running";
+ last if defined $instance->ip_address;
+ sleep 5;
+ my $reservations = $ec2->describe_instances(InstanceId =>
$vmId);
+ die "could not query EC2 instance: “" .
@{$reservations->errors}[0]->message . "”\n"
+ if ref $reservations ne "ARRAY";
+ $instance = @{@{$reservations}[0]->instances_set}[0];
+ }
+ print STDERR "\n";
+
+ my $ipv4 = $instance->ip_address;
+ print STDERR "got IP address ‘$ipv4’\n";
+
+ $state->{machines}->{$name} =
+ { targetEnv => $machine->{targetEnv}
+ , vmId => $vmId
+ , ipv4 => $ipv4
+ , reservation => $reservation->reservation_id
+ , privateIpv4 => $instance->private_ip_address
+ , dnsName => $instance->dns_name
+ , privateDnsName => $instance->private_dns_name
+ , timeCreated => time()
+ , ec2 => $machine->{ec2}
+ };
+
+ writeState;
+
+ print STDERR "checking whether VM ‘$name’ is reachable via
SSH...\n";
+
+ system "ssh -o StrictHostKeyChecking=no root\@$ipv4 true <
/dev/null 2> /dev/null";
+ die "cannot SSH to VM: $?" unless $? == 0;
}
}
@@ -329,7 +426,7 @@
# Figure out how we're gonna SSH to each machine. Prefer IPv6
# addresses over hostnames.
while (my ($name, $machine) = each %{$state->{machines}}) {
- $machine->{sshName} = $machine->{ipv6} || $machine->{targetHost} ||
die "don't know how to reach ‘$name’";
+ $machine->{sshName} = $machine->{ipv6} || $machine->{ipv4} ||
$machine->{targetHost} || die "don't know how to reach ‘$name’";
}
# So now that we know the hostnames / IP addresses of all
@@ -340,16 +437,19 @@
foreach my $name (keys %{$spec->{machines}}) {
my $machine = $state->{machines}->{$name};
$hosts .= "$machine->{ipv6} $name\\n" if defined $machine->{ipv6};
+ $hosts .= "$machine->{ipv4} $name\\n" if defined $machine->{ipv4};
}
open STATE, ">physical.nix" or die;
print STATE "{\n";
foreach my $name (keys %{$spec->{machines}}) {
my $machine = $state->{machines}->{$name};
- print STATE " $name = { config, pkgs, ... }:\n";
+ print STATE " $name = { config, pkgs, modulesPath, ... }:\n";
print STATE " {\n";
if ($machine->{targetEnv} eq "adhoc") {
print STATE " require = [ $myDir/adhoc-cloud-vm.nix ];\n";
+ } elsif ($machine->{targetEnv} eq "ec2") {
+ print STATE " require = [
\"\${modulesPath}/virtualisation/amazon-config.nix\" ];\n";
}
print STATE " networking.extraHosts = \"$hosts\";\n";
print STATE " };\n";
@@ -361,7 +461,7 @@
sub buildConfigs {
print STDERR "building all machine configurations...\n";
- my $vmsPath = `nix-build $myDir/eval-machine-info.nix --arg networkExprs
'[ @networkExprs ./physical.nix ]' -A machines`;
+ my $vmsPath = `nix-build --show-trace $myDir/eval-machine-info.nix --arg
networkExprs '[ @networkExprs ./physical.nix ]' -A machines`;
die "unable to build all machine configurations" unless $? == 0;
chomp $vmsPath;
return $vmsPath;
_______________________________________________
nix-commits mailing list
[email protected]
http://mail.cs.uu.nl/mailman/listinfo/nix-commits