sub load()

in managementnode/lib/VCL/Module/Provisioning/docker.pm [84:341]


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