Author: yoh
Date: Fri Feb 10 19:46:26 2017
New Revision: 1782510

URL: http://svn.apache.org/viewvc?rev=1782510&view=rev
Log:
VCL/VCL-854 Docker provisioning module
:https://issues.apache.org/jira/browse/VCL-854

This is a docker provisioning module for VCL and has been tested in VCL2.4.2 
and Docker version 1.12.6. 
All the impelentation details and configurations will be shown in the Jira 
issue soon.


Added:
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning/docker.pm

Added: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/docker.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/docker.pm?rev=1782510&view=auto
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/docker.pm (added)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/docker.pm Fri Feb 10 
19:46:26 2017
@@ -0,0 +1,1043 @@
+#!/usr/bin/perl -w
+###############################################################################
+# $Id: docker.pm 1604043 2017-02-10 12:00:00Z ohy $
+###############################################################################
+# 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.4.2';
+
+# 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


Reply via email to