managementnode/lib/VCL/Module/Provisioning/docker.pm (645 lines of code) (raw):

#!/usr/bin/perl -w ############################################################################### # $Id$ ############################################################################### # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### =head1 NAME VCL::Provisioning::docker - VCL module to support povisioning of docker =head1 SYNOPSIS Needs to be written =head1 DESCRIPTION This module provides... =cut ############################################################################## package VCL::Module::Provisioning::docker; # Specify the lib path using FindBin use FindBin; use lib "$FindBin::Bin/../../.."; # Configure inheritance use base qw(VCL::Module::Provisioning); # Specify the version of this module our $VERSION = '2.5.1'; # Specify the version of Perl to use use 5.008000; use strict; use warnings; use diagnostics; use English qw(-no_match_vars); use VCL::utils; use LWP::UserAgent; use JSON qw(from_json to_json encode_json decode_json); ############################################################################## =head1 OBJECT METHODS =cut sub initialize { my $self = shift; notify($ERRORS{'DEBUG'}, 0, "Docker module initialized"); return 1; } ## end sub initialize #///////////////////////////////////////////////////////////////////////////// =head2 load Parameters : Returns : Description : =cut sub load { my $self = shift; if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $image_name = $self->data->get_image_name() || return; my $computer_id = $self->data->get_computer_id() || return; my $computer_name = $self->data->get_computer_short_name() || return; my $vmhost_public_ip_address = $self->vmhost_os->data->get_computer_public_ip_address(0) || return; my $image_os_type = $self->data->get_image_os_type() || return; # The docker daemon listens on unix:///var/run/docker.sock # but you can Bind Docker to another host/port or a Unix socket # set Docker daemon to listen on a specific IP and port # set Docker host ip / port (/etc/init/docker.conf in Ubuntu 14.04 ) # (/usr/lib/systemd/system/docker.service in CentOS 7) # e.g.,) docker -H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock -d & my $docker_host_port = get_variable("docker_host_port"); # set docker host port in variable table, e.g., 4243 my $docker_host_url = "http://$vmhost_public_ip_address:$docker_host_port"; my $nathost_name = $self->data->get_nathost_hostname(0); my $vmhost_name = $self->data->get_vmhost_short_name() || return; my $using_nat_host = 0; if (defined($nathost_name)) { notify($ERRORS{'DEBUG'}, 0, "the VM hostname: $vmhost_name for computer: $computer_name is a nat host: $nathost_name"); notify($ERRORS{'DEBUG'}, 0, "computer: $computer_name will use random ports for nat"); $using_nat_host = 1; } # set ssh port of the vm_host (if not 22) my $remote_connection_target = determine_remote_connection_target($vmhost_name); # set the specific ssh port for vmhost in the variable table. e.g., 24 notify($ERRORS{'OK'}, 0, "remote_connection_target: $remote_connection_target"); my $target_ssh_port = get_variable("vmhost_ssh_port") || 22; $ENV->{ssh_port}->{$remote_connection_target} = $target_ssh_port; notify($ERRORS{'OK'}, 0, "vmhost_ssh_port: $target_ssh_port"); # create a useragent my $ua = LWP::UserAgent->new(); # Using docker inspect to check whether there is any container with the same computer name or not # if it exists, unload the previous one my $resp = $ua->get( $docker_host_url . "/containers/$computer_name/json", content_type => 'application/json', ); my $container_exist = 0; eval { from_json($resp->content); $container_exist = 1; } or do { notify($ERRORS{'DEBUG'}, 0, "the container id for $computer_name does not exist: " . join("\n", $resp->content)); }; if ($container_exist) { my $output = from_json($resp->content); my $container_id = $output->{'Id'}; notify($ERRORS{'OK'}, 0, "container_id: [$container_id]"); if (defined($container_id)) { notify($ERRORS{'WARNING'}, 0, "the container id for $computer_name already exists"); if(!$self->unload()) { notify($ERRORS{'WARNING'}, 0, "failed to unload the container for $computer_name"); return 0; } } } # set the number of CPUs and memory size for the container my $cpu_count = $self->data->get_image_minprocnumber() || 1; notify($ERRORS{'OK'}, 0, "cpu_count: [$cpu_count]"); my $cpus = "0"; if ($cpu_count == 1) { $cpus = "0"; } else { $cpu_count = $cpu_count - 1; $cpus = "0-$cpu_count"; } my $memory_mb = $self->data->get_image_minram(); my $memory_bytes = ($memory_mb * 1024 * 1024); $memory_bytes = 0 + $memory_bytes; my $eth0_mac_address = $self->data->get_computer_eth0_mac_address(); notify($ERRORS{'OK'}, 0, "eth0 mac: $eth0_mac_address, cpus: [$cpus], memory_bytes: [$memory_bytes]"); if (!defined($eth0_mac_address) || !defined($memory_bytes)) { notify($ERRORS{'WARNINGS'}, 0, "eth0 mac address:[$eth0_mac_address] OR memory size: [$memory_bytes] is not defined for $computer_name"); return; } my $docker_default_ssh_port = get_variable("docker_default_ssh_port") || 22; # set docker container default ssh port in variable table, e.g., 22 my $docker_default_public_port = get_default_public_port($image_os_type); if (!defined($docker_default_public_port)) { notify($ERRORS{'WARNING'}, 0, "failed to get default public port for $computer_name"); return 0; } my $container_data; # json docker create format of the container if ($using_nat_host) { # get a random ssh port for the container node for public access my $random_ssh_port = $self->get_dockerhost_random_port($docker_default_ssh_port); # get a random public port for public access of the applications (e.g., xrdp, lxde, rstudio) my $random_public_port = $self->get_dockerhost_random_port($docker_default_public_port); #my $random_vnc_port = $self->get_dockerhost_random_port($docker_default_vnc_port); notify($ERRORS{'DEBUG'}, 0, "default_ssh_port: $docker_default_ssh_port, random_public_port: $docker_default_public_port"); notify($ERRORS{'DEBUG'}, 0, "random_ssh_port: $random_ssh_port, random_public_port: $random_public_port"); if (!defined($random_ssh_port) || !defined($random_public_port)) { notify($ERRORS{'WARNING'}, 0, "failed to get ssh: $random_ssh_port, public: $random_public_port port for $computer_name"); return 0; } $container_data = { Image => $image_name, # MacAddress => $eth0_mac_address, # It could be used for future DHCP configuration. HostConfig => { Privileged => JSON::true, # 0/1 and true/false cause GO Marshall converting error #Memory => 0 + $memory_bytes, # add 0 to avoid JSON format error (GO Marshall converting error) # CpusetCpus => "$cpus", # This fixed and biased distribution would reduce the CPU utilization. Need more fair distribution methods. # Docker uses all the available CPUs equally(Only set if you have better solutions) CapAdd => ["NET_ADMIN"], #// allow to use iptables inside a container #PublishAllPorts => JSON::true, #NetworkMode => 'none' # default NetworkMode is bridge PortBindings => { "$docker_default_ssh_port\/tcp" => [{HostPort => $random_ssh_port }], "$docker_default_public_port\/tcp" => [{HostPort => $random_public_port }], # Examples #"8080/tcp" => [{ HostPort => "" }], #"8088/tcp" => [{ HostPort => "" }], #"8443/tcp" => [{ HostPort => "" }], #"18080/tcp" => [{ HostPort => "" }], #"$docker_default_vnc_port\/tcp" => [{HostPort => $random_vnc_port }] #"$docker_default_ssh_port\/tcp" => [{ HostIp => $vmhost_public_ip_address, HostPort => $random_ssh_port }], #"$docker_default_public_port\/tcp" => [{ HostIp => $vmhost_public_ip_address, HostPort => $random_public_port }] }, }, }; } else { $container_data = { Image => $image_name, # MacAddress => $eth0_mac_address, # It could be used for future DHCP configuration. HostConfig => { # Privileged => JSON::true, # 0/1 and true/false cause GO Marshall converting error ##Memory => 0 + $memory_bytes, # add 0 to avoid JSON format error (GO Marshall converting error) # CpusetCpus => "$cpus", # This fixed and biased distribution would reduce the CPU utilization. Need more fair distribution methods. # Docker uses all the available CPUs equally(Only set if you have better solutions) CapAdd => ["NET_ADMIN"], #// allow to use iptables inside a container NetworkMode => 'none' # default NetworkMode is bridge }, }; } # create the container $resp = $ua->post( $docker_host_url . "/containers/create?name=$computer_name", content_type => 'application/json', content => to_json($container_data), ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to create a container for $computer_name: " . join("\n", $resp->content)); return; } notify($ERRORS{'DEBUG'}, 0, "successfully create a container: ". join("\n", $resp->content)); # start the container $resp = $ua->post( $docker_host_url . "/containers/$computer_name/start", content_type => 'application/json', ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to start the container for $computer_name: " . join("\n", $resp->content)); return; } notify($ERRORS{'DEBUG'}, 0, "successfully start the container: ". join("\n", $resp->content)); $resp = $ua->get( $docker_host_url . "/containers/$computer_name/json", content_type => 'application/json', ); eval { from_json($resp->content); 1; } or do { notify($ERRORS{'DEBUG'}, 0, "the container id for $computer_name does not exist: " . join("\n", $resp->content)); return; }; my $output = from_json($resp->content); my $container_pid = $output->{State}{Pid}; if (!defined($container_pid)) { notify($ERRORS{'WARNING'}, 0, "failed to get Pid of the container on $computer_name"); return; } # if docker uses dhcp server to assign IPs to computer if ($using_nat_host) { my $ua_output = from_json($resp->content); my $private_ip = $ua_output->{NetworkSettings}{IPAddress}; if (!defined($private_ip)) { notify($ERRORS{'WARNINGS'}, 0, "private IP address is not defined for $computer_name"); return; } # YOUNG update /etc/hosts `sed -i "/.*\\b$computer_name\$/d" /etc/hosts`; `echo "$private_ip\t$computer_name" >> /etc/hosts`; # update the private ip address of the computer my $result = update_computer_private_ip_address($computer_id, $private_ip); if (!defined($result)) { notify($ERRORS{'WARNING'}, 0, "failed to update $private_ip on $computer_name"); return 0; } notify($ERRORS{'DEBUG'}, 0, "private IP address is $private_ip for $computer_name"); } else { # add eth0 to the container #my $private_nic = get_variable("docker_container_private_nic"); #my $private_bridge = get_variable("docker_host_privae_bridge"); my $container_private_nic = "eth0"; my $private_bridge = 'br0'; #my $private_bridge = $self->data->get_vmhost_profile_virtualswitch0(0) || 'br0'; if(!$self->create_eth_inside_container($container_pid, $container_private_nic, $private_bridge)) { notify($ERRORS{'WARNING'}, 0, "failed to create eth0 inside the container on $computer_name"); return; } # add eth1 to the container #my $public_nic = get_variable("docker_container_public_nic"); #my $public_bridge = get_variable("docker_host_public_bridge"); my $container_public_nic = "eth1"; my $public_bridge = 'br1'; #my $public_bridge = $self->data->get_vmhost_profile_virtualswitch1(0) || 'br1'; if(!$self->create_eth_inside_container($container_pid, $container_public_nic, $public_bridge)) { notify($ERRORS{'WARNING'}, 0, "failed to create eth1 inside the container on $computer_name"); return; } # sleep to get dhcp sleep(20); } # Call post_load if ($self->os->can("post_load")) { notify($ERRORS{'DEBUG'}, 0, "calling " . ref($self->os) . "->post_load()"); if ($self->os->post_load()) { notify($ERRORS{'DEBUG'}, 0, "successfully ran OS post_load subroutine"); } else { notify($ERRORS{'WARNING'}, 0, "failed to run OS post_load subroutine"); return; } } else { notify($ERRORS{'WARNING'}, 0, ref($self->os) . "::post_load() has not been implemented"); return; } return 1; } ## end sub load #///////////////////////////////////////////////////////////////////////////// =head2 create_eth_inside_container Parameters : container process id Returns : 1(success) or 0(failure) Description : =cut sub create_eth_inside_container { my $self = shift; my $container_pid = shift; my $eth_name = shift; my $bridge_name = shift; notify($ERRORS{'DEBUG'}, 0, "container_pid: $container_pid"); notify($ERRORS{'DEBUG'}, 0, "ethernet_name: $eth_name"); if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } my $computer_name = $self->data->get_computer_short_name() || return; my $veth_eth_name = $eth_name . $container_pid; my $veth_name = "v" . $eth_name . $container_pid; # PreStep 1 my $netns_path = "/var/run/netns"; my $command = "mkdir -p /var/run/netns"; my ($exit_status, $output); if (!$self->vmhost_os->file_exists($netns_path)) { #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to create netns on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } } # PreStep 2 my $netns_veth_path = "/var/run/netns/$container_pid"; if (!$self->vmhost_os->file_exists($netns_veth_path)) { $command = "ln -s /proc/$container_pid/ns/net /var/run/netns/$container_pid"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to create netns on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } } # Step 1 $command = "ip link add $veth_name type veth peer name $veth_eth_name"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to create veth pair on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } # Step 2 $command = "brctl addif $bridge_name $veth_name"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to add interface to the veth on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } my $mac_address; if ($eth_name eq 'eth0') { $mac_address = $self->data->get_computer_eth0_mac_address(); notify($ERRORS{'OK'}, 0, "mac address of the eth0: $mac_address"); } elsif ($eth_name eq 'eth1') { $mac_address = $self->data->get_computer_eth1_mac_address(); notify($ERRORS{'OK'}, 0, "mac address of the eth1: $mac_address"); } if (!defined($mac_address)) { notify($ERRORS{'WARNING'}, 0, "failed to get mac address: $mac_address"); #return 0; } # Step 3 $command = "ip link set $veth_name up"; ($exit_status, $output) = $self->vmhost_os->execute($command); #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to set the interface up on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } # Step 4 $command = "ip link set $veth_eth_name netns $container_pid"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to set the interface up on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } # Step 5 $command = "ip netns exec $container_pid ip link set dev $veth_eth_name name $eth_name"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to set the interface up on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } # Step 6 $command = "ip netns exec $container_pid ip link set $eth_name address $mac_address"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to set the interface up on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } $command = "ip netns exec $container_pid ip link set $eth_name up"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to set the interface up on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } if ($eth_name eq 'eth1') { $command = "docker exec $computer_name dhclient"; #$command = "docker exec $computer_name dhclient eth0 eth1"; #($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to set the interface up on $container_pid\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); #return 0; } } return 1; } #///////////////////////////////////////////////////////////////////////////// =head2 unload Parameters : hash Returns : 1(success) or 0(failure) Description : loads virtual machine with requested image =cut sub unload { my $self = shift; if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $image_name = $self->data->get_image_name() || return; my $computer_name = $self->data->get_computer_short_name() || return; my $vmhost_name = $self->data->get_vmhost_short_name() || return; my $vmhost_public_ip_address = $self->vmhost_os->data->get_computer_public_ip_address(0) || return; my $vmhost_internal_ip_address = $self->vmhost_os->data->get_computer_private_ip_address(0) || return; my $docker_host_port = get_variable("docker_host_port"); # set docker host port in variable table, e.g., 4243 my $docker_host_url = "http://$vmhost_public_ip_address:$docker_host_port"; # set ssh port of the vm_host (if not 22) my $remote_connection_target = determine_remote_connection_target($vmhost_name); # set the specific ssh port for vmhost in the variable table. e.g., 24 my $target_ssh_port = get_variable("vmhost_ssh_port") || 22; $ENV->{ssh_port}->{$remote_connection_target} = $target_ssh_port; # create a useragent my $ua = LWP::UserAgent->new(); # stop the container my $resp = $ua->post( $docker_host_url . "/containers/$computer_name/stop", content_type => 'application/json', ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to stop the container for $computer_name: " . join("\n", $resp->content)); return; } sleep(1); $resp = $ua->delete( $docker_host_url . "/containers/$computer_name", content_type => 'application/json', ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to delete the container for $computer_name: " . join("\n", $resp->content)); return; } # delete dangled network namespace ids my $nathost_name = $self->data->get_nathost_hostname(0); if (!defined($nathost_name)) { notify($ERRORS{'DEBUG'}, 0, "docker container $computer_name is not running in a NATHOST"); my $command = "find -L /var/run/netns -type l -delete"; #my ($exit_status, $output) = $self->vmhost_os->execute($command,1,10,3,24); my ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "delete dangled veth on $computer_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); } } notify($ERRORS{'DEBUG'}, 0, "docker container $computer_name is completely removed"); sleep(10); return 1; } ## end sub unload #///////////////////////////////////////////////////////////////////////////// =head2 capture Parameters : None Returns : 1 if sucessful, 0 if failed Description : capturing a new OpenStack image. =cut sub capture { my $self = shift; if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } notify($ERRORS{'OK'}, 0, "Docker Capturing...."); my $old_image_name = $self->data->get_image_name(); my $new_image_name = $self->get_new_image_name(); $self->data->set_image_name($new_image_name); my $image_id = $self->data->get_image_id(); my $imagerevision_id = $self->data->get_imagerevision_id(); my $imagerevision_comments = $self->data->get_imagerevision_comments(0); my $computer_name = $self->data->get_computer_short_name(); my $vmhost_public_ip_address = $self->vmhost_os->data->get_computer_public_ip_address(0); my $docker_host_port = get_variable("docker_host_port"); # set docker host port in variable table, e.g., 4243 my $docker_host_url = "http://$vmhost_public_ip_address:$docker_host_port"; if ($self->os->can("pre_capture")) { notify($ERRORS{'OK'}, 0, "calling OS module's pre_capture() subroutine"); # do not turn the container off to run "docker commit" if (!$self->os->pre_capture({end_state => 'on'})) { notify($ERRORS{'WARNING'}, 0, "OS module pre_capture() failed"); return 0; } } my $ua = LWP::UserAgent->new(); my $resp = $ua->get( $docker_host_url . "/containers/$computer_name/json", content_type => 'application/json', ); eval { from_json($resp->content); 1; } or do { notify($ERRORS{'DEBUG'}, 0, "the container id for $computer_name does not exist: " . join("\n", $resp->content)); return 0; }; my $output = from_json($resp->content); my $container_id = $output->{'Id'}; notify($ERRORS{'OK'}, 0, "container_id: [$container_id]"); if (!defined($container_id)) { notify($ERRORS{'WARNING'}, 0, "failed to get the container id for $computer_name"); return 0; } $resp = $ua->post( $docker_host_url . "/commit?container=$container_id&comment=$imagerevision_comments&repo=$new_image_name", content_type => 'application/json', ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to commit the container for $computer_name: " . join("\n", $resp->content)); return; } # Update the image name in the database if ($old_image_name ne $new_image_name && !update_image_name($image_id, $imagerevision_id, $new_image_name)) { notify($ERRORS{'WARNING'}, 0, "failed to update image name in the database: $old_image_name --> $new_image_name"); return; } sleep(10); notify($ERRORS{'DEBUG'}, 0, "successfully captured the container for $computer_name"); if(!$self->unload()) { notify($ERRORS{'WARNING'}, 0, "failed to unload the container for $computer_name"); return 0; } return 1; } ## end sub capture #///////////////////////////////////////////////////////////////////////////// =head2 get_image_size Parameters : imagename Returns : 0 failure or size of image Description : in size of Megabytes =cut sub get_image_size { my $self = shift; if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } my $image_name = $self->data->get_image_name() || return; my $vmhost_public_ip_address = $self->vmhost_os->data->get_computer_public_ip_address(0); my $docker_host_port = get_variable("docker_host_port"); # set docker host port in variable table, e.g., 4243 my $docker_host_url = "http://$vmhost_public_ip_address:$docker_host_port"; # create a useragent my $ua = LWP::UserAgent->new(); my $resp = $ua->get( $docker_host_url . "/images/json?filter=$image_name", content_type => 'application/json', ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to get image info: " . join("\n", $resp->content)); return 0; } my $output = from_json($resp->content); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); return 0; } my $image_size = $output->[0]{'VirtualSize'}; if (!defined($image_size)) { notify($ERRORS{'WARNING'}, 0, "The docker image size for $image_name does not be defined"); return 0; } else { notify($ERRORS{'OK'}, 0, "The docker image size for $image_name is $image_size"); return round($image_size); # Mbytes } } ## end sub get_image_size #///////////////////////////////////////////////////////////////////////////// =head2 power_reset Parameters : $computer_node_name (optional) Returns : boolean Description : Powers off and then powers on the computer. =cut sub power_reset() { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } ## To DO (YOUNG) # restart the docker container remove the veth notify($ERRORS{'OK'}, 0, "Power_reset does NOT support for Docker"); return 1; my $computer_name = $self->data->get_computer_short_name() || return; notify($ERRORS{'OK'}, 0, "computer_name: $computer_name"); my $vmhost_public_ip_address = $self->vmhost_os->data->get_computer_public_ip_address(0); my $docker_host_port = get_variable("docker_host_port"); # set docker host port in variable table, e.g., 4243 my $docker_host_url = "http://$vmhost_public_ip_address:$docker_host_port"; # create a useragent my $ua = LWP::UserAgent->new(); my $resp = $ua->post( $docker_host_url . "/containers/$computer_name/restart", content_type => 'application/json', ); if (!$resp->is_success) { notify($ERRORS{'WARNING'}, 0, "failed to restart the container for $computer_name: " . join("\n", $resp->content)); return; } notify($ERRORS{'OK'}, 0, "The docker container is successfully restarted"); return 1; } ## end sub power_reset #///////////////////////////////////////////////////////////////////////////// =head2 node_status Parameters : [0]: computer node name (optional) [1]: log file path (optional) Returns : Depends on the context which node_status was called: default: string containing "READY" or "FAIL" boolean: true if ping, SSH, and VCL client checks are successful false if any checks fail list: array, values are 1 for SUCCESS, 0 for FAIL [0]: Node status ("READY" or "FAIL") [1]: Ping status (0 or 1) [2]: SSH status (0 or 1) [3]: VCL client daemon status (0 ir 1) arrayref: reference to array described above hashref: reference to hash with keys/values: {status} => <"READY","FAIL"> {ping} => <0,1> {ssh} => <0,1> {vcl_client} => <0,1> Description : Checks the status of a lab machine. Checks if the machine is pingable, can be accessed via SSH, and the VCL client is running. =cut sub node_status{ my $self; # Get the argument my $argument = shift; # Check if this subroutine was called an an object method or an argument was passed if (ref($argument) =~ /VCL::Module/i) { $self = $argument; } elsif (!ref($argument) || ref($argument) eq 'HASH') { # An argument was passed, check its type and determine the computer ID my $computer_id; if (ref($argument)) { # Hash reference was passed $computer_id = $argument->{id}; } elsif ($argument =~ /^\d+$/) { # Computer ID was passed $computer_id = $argument; } else { # Computer name was passed ($computer_id) = get_computer_ids($argument); } if ($computer_id) { notify($ERRORS{'DEBUG'}, 0, "computer ID: $computer_id"); } else { notify($ERRORS{'WARNING'}, 0, "unable to determine computer ID from argument:\n" . format_data($argument)); return; } # Create a DataStructure object containing data for the computer specified as the argument my $data; eval { $data= new VCL::DataStructure({computer_identifier => $computer_id}); }; if ($EVAL_ERROR) { notify($ERRORS{'WARNING'}, 0, "failed to create DataStructure object for computer ID: $computer_id, error: $EVAL_ERROR"); return; } elsif (!$data) { notify($ERRORS{'WARNING'}, 0, "failed to create DataStructure object for computer ID: $computer_id, DataStructure object is not defined"); return; } else { notify($ERRORS{'DEBUG'}, 0, "created DataStructure object for computer ID: $computer_id"); } # Create a VMware object my $object_type = 'VCL::Module::Provisioning::docker'; if ($self = ($object_type)->new({data_structure => $data})) { notify($ERRORS{'DEBUG'}, 0, "created $object_type object to check the status of computer ID: $computer_id"); } else { notify($ERRORS{'WARNING'}, 0, "failed to create $object_type object to check the status of computer ID: $computer_id"); return; } # Create an OS object for the VMware object to access if (!$self->create_os_object()) { notify($ERRORS{'WARNING'}, 0, "failed to create OS object"); return; } } # Create a hash reference and populate it with the default values my $status; $status->{currentimage} = ''; $status->{ssh} = 0; $status->{image_match} = 0; $status->{status} = 'RELOAD'; return $status; } ## end sub node_status #///////////////////////////////////////////////////////////////////////// =head2 does_image_exist Parameters : Returns : 1 or 0 Description : Checks the existence of an image. =cut sub does_image_exist { my $self = shift; if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } my $image_name = $self->data->get_image_name(); my $vmhost_public_ip_address = $self->vmhost_os->data->get_computer_public_ip_address(0); my $docker_host_port = get_variable("docker_host_port"); # set docker host port in variable table, e.g., 4243 my $docker_host_url = "http://$vmhost_public_ip_address:$docker_host_port"; notify($ERRORS{'DEBUG'}, 0, "find image name: $image_name in docker host: $docker_host_url"); my $ua = LWP::UserAgent->new(); my $resp = $ua->get( $docker_host_url . "/images/$image_name/json", content_type => 'application/json', ); eval { from_json($resp->content); 1; } or do { notify($ERRORS{'DEBUG'}, 0, "the image id for $image_name does not exist: " . join("\n", $resp->content)); return 0; }; my $output = from_json($resp->content); my $image_id = $output->{'Id'}; notify($ERRORS{'OK'}, 0, "image_id: [$image_id]"); if (!defined($image_id)) { notify($ERRORS{'WARNING'}, 0, "The docker image for $image_name does not exist"); return 0; } else { notify($ERRORS{'OK'}, 0, "The docker image for $image_name exists"); return 1; } } ## end sub does_image_exist #///////////////////////////////////////////////////////////////////////////// =head2 get_default_public_port Parameters : Returns : string Description : Find the default public port for the image based on the connect method =cut sub get_default_public_port { my $image_os_type = shift; if (!defined($image_os_type)) { notify($ERRORS{'WARNING'}, 0, "failed to get image os type"); return; } my $sql_statement = <<EOF; SELECT port FROM connectmethodport WHERE connectmethodid = ( SELECT id FROM connectmethod WHERE name = '$image_os_type') EOF notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); my @selected_rows = database_select($sql_statement); if (scalar @selected_rows == 0 || scalar @selected_rows > 1) { notify($ERRORS{'WARNING'}, 0, "" . scalar @selected_rows . " rows were returned from database select"); return; } my $default_public_port = $selected_rows[0]{port}; if (!defined($default_public_port)) { notify($ERRORS{'WARNING'}, 0, "failed to get default public port"); return; } notify($ERRORS{'DEBUG'}, 0, "default public port for $image_os_type is $default_public_port"); return $default_public_port; } #///////////////////////////////////////////////////////////////////////////// =head2 get_new_image_name Parameters : Returns : string Description : Constructs a new image name for images being captured. This is used instead of the name in the database in case an image is converted. The new image name shouldn't contain vmware. =cut sub get_new_image_name { my $self = shift; if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $image_id = $self->data->get_image_id(); my $image_os_name = $self->data->get_image_os_name(); my $image_prettyname = $self->data->get_image_prettyname(); my $imagerevision_revision = $self->data->get_imagerevision_revision(); # Clean up the OS name $image_os_name = "docker"; # Remove non-word characters and underscores from the image prettyname $image_prettyname =~ s/[\W\_]+//ig; my $new_image_name = "$image_os_name\-$image_prettyname"."$image_id\-v$imagerevision_revision"; notify($ERRORS{'DEBUG'}, 0, "new_image_name: [$new_image_name]"); return $new_image_name; } ## end sub get_new_image_name #///////////////////////////////////////////////////////////////////////////// =head2 get_dockerhost_random_port Parameters : $remote_ip (optional), $overwrite Returns : ssh_port or public_port Description : Processes the random port for the connect methods configured for the image revision. =cut sub get_dockerhost_random_port{ my $self = shift; my $default_port = shift; notify($ERRORS{'DEBUG'}, 0, "default_port: $default_port"); if (ref($self) !~ /docker/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $image_name = $self->data->get_image_name(); my $computer_node_name = $self->data->get_computer_node_name(); my $nathost_hostname = $self->data->get_nathost_hostname(0); my $nathost_public_ip_address = $self->data->get_nathost_public_ip_address(0); # Retrieve the connect method info hash my $connect_method_info = $self->data->get_connect_methods(); if (!$connect_method_info) { notify($ERRORS{'WARNING'}, 0, "failed to retrieve connect method info"); return; } CONNECT_METHOD: for my $connect_method_id (sort keys %{$connect_method_info} ) { my $connect_method = $connect_method_info->{$connect_method_id}; for my $connect_method_port_id (keys %{$connect_method->{connectmethodport}}) { my $protocol = $connect_method->{connectmethodport}{$connect_method_port_id}{protocol}; my $port = $connect_method->{connectmethodport}{$connect_method_port_id}{port}; if ($port == $default_port) { my $random_port = $connect_method->{connectmethodport}{$connect_method_port_id}{natport}{publicport}; if (!defined($random_port)) { notify($ERRORS{'WARNING'}, 0, "$computer_node_name is assigned to NAT host $nathost_hostname but connect method info does not contain NAT port information:\n" . format_data($connect_method)); return; } else { return $random_port; } } # notify($ERRORS{'WARNING'}, 0, "$computer_node_name is assigned to NAT host $nathost_hostname but connect method info does not contain NAT port information:\n" . format_data($connect_method)); } } return; } ## end sub get_dockerhost_random_port #///////////////////////////////////////////////////////////////////////////// 1; __END__ =head1 SEE ALSO L<http://cwiki.apache.org/VCL/> =cut