| #!/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 |