Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/openstack.pm URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/openstack.pm?rev=1798487&r1=1798486&r2=1798487&view=diff ============================================================================== --- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/openstack.pm (original) +++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/openstack.pm Mon Jun 12 16:33:35 2017 @@ -1,1417 +1,1417 @@ -#!/usr/bin/perl -w -############################################################################### -# $Id: openstack.pm -############################################################################### -# 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::openstack - VCL module to support the Openstack provisioning engine with REST APIs v2 - -=head1 SYNOPSIS - - Needs to be written - -=head1 DESCRIPTION - -This module provides VCL support for Openstack - -=cut - -############################################################################### -package VCL::Module::Provisioning::openstack; - -# Include File Copying for Perl -use File::Copy; - -# 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 IO::File; -use Fcntl qw(:DEFAULT :flock); -use File::Temp qw(tempfile); -use List::Util qw(max); -use VCL::utils; -use JSON qw(from_json to_json); -use LWP::UserAgent; - -#////////////////////////////////////////////////////////////////////////////// - -=head2 initialize - - Parameters : - Returns : - Description : - -=cut - -sub initialize { - my $self = shift; - notify($ERRORS{'DEBUG'}, 0, "OpenStack module initialized"); - - if ($self->_set_os_auth_conf()) { - notify($ERRORS{'OK'}, 0, "successfully set openStack auth configuration"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to set openstack auth configuration"); - return 0; - } - - return 1; -} ## end sub initialize - - -#////////////////////////////////////////////////////////////////////////////// - -=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) !~ /openstack/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return; - } - - my $computer_name = $self->data->get_computer_short_name() || return; - my $vmhost_name = $self->data->get_vmhost_short_name() || return; - my $computer_private_ip_address = $self->data->get_computer_private_ip_address(); - - # Remove existing VMs which were created for the reservation computer - if (_pingnode($computer_private_ip_address)) { - if (!$self->_terminate_os_instance()) { - notify($ERRORS{'WARNING'}, 0, "failed to delete VM $computer_name on VM host $vmhost_name"); - return 0; - } - } - # Remove existing openstack id for computer mapping in database - # Althought the instance is not pingable (delete it accidently), it should delete the instance from database - if (!$self->_delete_os_computer_mapping()) { - notify($ERRORS{'WARNING'}, 0, "failed to delete the openstack instance id from openstackcomputermap"); - return 0; - } - - return 1; - -} - -#////////////////////////////////////////////////////////////////////////////// - -=head2 provision - - Parameters : hash - Returns : 1(success) or 0(failure) - Description : loads virtual machine with requested image - -=cut - -sub load { - my $self = shift; - if (ref($self) !~ /openstack/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return; - } - - my $reservation_id = $self->data->get_reservation_id() || return; - my $computer_id = $self->data->get_computer_id() || return; - my $computer_name = $self->data->get_computer_short_name() || return; - my $image_name = $self->data->get_image_name() || return; - my $vmhost_name = $self->data->get_vmhost_short_name() || return; - my $computer_private_ip_address = $self->data->get_computer_private_ip_address() || return; - - insertloadlog($reservation_id, $computer_id, "startload", "$computer_name $image_name"); - notify($ERRORS{'DEBUG'}, 0, "computer_private_ip_address = [$computer_private_ip_address]"); - - # Remove existing VMs which were created for the reservation computer - if (_pingnode($computer_private_ip_address)) { - if (!$self->_terminate_os_instance()) { - notify($ERRORS{'CRITICAL'}, 0, "failed to delete VM $computer_name on VM host $vmhost_name"); - } - } - # Remove existing openstack id for computer mapping in database - # Althought the instance is not pingable (delete it accidently), it should delete the instance from database - if (!$self->_delete_os_computer_mapping()) { - notify($ERRORS{'WARNING'}, 0, "failed to delete the openstack instance id from openstackcomputermap"); - return; - } - - # Create new instance - my $os_instance_id = $self->_post_os_create_instance(); - if (!defined($os_instance_id)) { - notify($ERRORS{'CRITICAL'}, 0, "failed to create an instance for computer $computer_name on VM host: $vmhost_name"); - return; - } - - # Update the private ip of the instance in database - if (!$self->_update_private_ip($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to update private ip of the instance in database"); - return; - } - - # 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 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) !~ /openstack/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return; - } - - my $reservation_id = $self->data->get_reservation_id() || return; - my $current_imagerevision_id = $self->os->get_current_imagerevision_id(); - my $computer_id = $self->data->get_computer_id() || return; - my $image_name = $self->data->get_image_name() || return; - my $computer_name = $self->data->get_computer_short_name() || return; - my $computer_private_ip_address = $self->data->get_computer_private_ip_address() || return; - - insertloadlog($reservation_id, $computer_id, "startcapture", "$computer_name $image_name"); - notify($ERRORS{'DEBUG'}, 0, "computer_private_ip_address = [$computer_private_ip_address]"); - - # Remove existing VMs which were created for the reservation computer - if (!_pingnode($computer_private_ip_address)) { - notify($ERRORS{'WARNING'}, 0, "unable to ping to $computer_name"); - return; - } - - my $os_instance_id = $self->_get_os_instance_id(); - if (!defined($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "unable to get instance id for $computer_name"); - return; - } - notify($ERRORS{'DEBUG'}, 0, "os_instance_id: $os_instance_id"); - - my $os_flavor_id = _get_os_flavor_id($current_imagerevision_id); - notify($ERRORS{'DEBUG'}, 0, "current imagerevision id is $current_imagerevision_id, flavor_id: $os_flavor_id"); - if (!defined($os_flavor_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get current openstack flavor id"); - return; - } - - if (!$self->_prepare_capture()) { - notify($ERRORS{'WARNING'}, 0, "failed to execute prepare_capture"); - return; - } - - my $os_image_id = $self->_post_os_create_image($os_instance_id); - if (!defined($os_image_id)) { - notify($ERRORS{'CRITICAL'}, 0, "failed to create image for $computer_name"); - return; - } - notify($ERRORS{'DEBUG'}, 0, "os_image_id: $os_image_id"); - - if (!$self->_wait_for_copying_image($os_image_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to execute _wait_for_copying_image for $os_image_id"); - return; - } - - # insert image details and flavor details, check status is ACTIVE before insert - if (!$self->_insert_os_image_id($os_image_id, $os_flavor_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to insert openstack image id"); - return; - } - notify($ERRORS{'DEBUG'}, 0, "capturing $os_instance_id into $os_image_id is done"); - - return 1; -} ## end sub capture - -#////////////////////////////////////////////////////////////////////////////// - -=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) !~ /openstack/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return 0; - } - - my $imagerevision_id = $self->data->get_imagerevision_id() || return 0; - my $image_name = $self->data->get_image_name() || return 0; - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth info"); - return 0; - } - - # Get the openstack image id for the corresponding VCL image revision id - my $os_image_id = _get_os_image_id($imagerevision_id); - if (!defined($os_image_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to acquire the openstack image id : $os_image_id"); - return 0; - } - - my $ua = LWP::UserAgent->new(); - my $resp = $ua->get( - $os_compute_url . "/images/" . $os_image_id, - x_auth_token => $os_token, - x_auth_project_id => $os_project_id, - ); - - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to execute post token: " . join("\n", $resp->content)); - return 0; - } - - my $output = from_json($resp->content); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json ouput: $output"); - return 0; - } - - my $image_status = $output->{image}{status}; - if (defined($image_status) && $image_status eq 'ACTIVE') { - notify($ERRORS{'OK'}, 0, "The openstack image for $image_name exists"); - return 1; - } - else { - notify($ERRORS{'WARNING'}, 0, "The openstack image for $image_name does NOT exists"); - return 0; - } - -} ## end sub does_image_exist - -#////////////////////////////////////////////////////////////////////////////// - -=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) !~ /openstack/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return; - } - - # Attempt to get the image name argument - my $image_name = shift; - my $imagerevision_id = $self->data->get_imagerevision_id() || return; - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth info"); - return; - } - my $os_image_id = _get_os_image_id($imagerevision_id); - if (!defined($os_image_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to acquire the openstack image id : $os_image_id for $image_name"); - return; - } - - my $ua = LWP::UserAgent->new(); - my $resp = $ua->get( - $os_compute_url . "/images/" . $os_image_id, - x_auth_token => $os_token, - x_auth_project_id => $os_project_id, - ); - - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to execute post token: " . join("\n", $resp->content)); - return; - } - - my $output = from_json($resp->content); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json ouput: $output"); - return; - } - - my $os_image_size_bytes = $output->{'image'}{'OS-EXT-IMG-SIZE:size'}; - if (!defined($os_image_size_bytes)) { - notify($ERRORS{'WARNING'}, 0, "The openstack image size for $image_name does NOT exists"); - return; - } - - notify($ERRORS{'DEBUG'}, 0, "os_image_size_bytes: $os_image_size_bytes for $image_name"); - return round($os_image_size_bytes / 1024 / 1024); -} ## end sub get_image_size - -#////////////////////////////////////////////////////////////////////////////// - -=head2 node_status - - Parameters : $computer_id or $hash->{computer}{id} (optional) - Returns : string -- 'READY', 'POST_LOAD', or 'RELOAD' - Description : Checks the status of a VM. 'READY' is returned if the VM is - accessible via SSH, and the OS module's post-load tasks have - run. 'POST_LOAD' is returned if the VM only needs to have - the OS module's post-load tasks run before it is ready. - 'RELOAD' is returned otherwise. - -=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::openstack'; - 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; - } - } - - my $reservation_id = $self->data->get_reservation_id(); - my $computer_name = $self->data->get_computer_node_name(); - my $image_name = $self->data->get_image_name(); - my $request_forimaging = $self->data->get_request_forimaging(); - my $imagerevision_id = $self->data->get_imagerevision_id(); - my $computer_private_ip_address = $self->data->get_computer_private_ip_address(); - - - notify($ERRORS{'DEBUG'}, 0, "attempting to check the status of computer $computer_name, image: $image_name"); - - # 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'; - - # Check if node is pingable and retrieve the power status if the reservation ID is 0 - # The reservation ID will be 0 is this subroutine was not called as an object method, but with a computer ID argument - # The reservation ID will be 0 when called from healthcheck.pm - # The reservation ID will be > 0 if called from a normal VCL reservation - # Skip the ping and power status checks for a normal reservation to speed things up - if (!$reservation_id) { - if (_pingnode($computer_private_ip_address)) { - notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is pingable"); - $status->{ping} = 1; - } - else { - notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is not pingable"); - $status->{ping} = 0; - } - - } - - notify($ERRORS{'DEBUG'}, 0, "Trying to ssh..."); - # Check if SSH is available - if ($self->os->is_ssh_responding()) { - notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is responding to SSH"); - $status->{ssh} = 1; - } - else { - notify($ERRORS{'OK'}, 0, "VM $computer_name is not responding to SSH, returning 'RELOAD'"); - $status->{status} = 'RELOAD'; - $status->{ssh} = 0; - - # Skip remaining checks if SSH isn't available - return $status; - } - - my $current_image_revision_id = $self->os->get_current_imagerevision_id(); - $status->{currentimagerevision_id} = $current_image_revision_id; - - $status->{currentimage} = $self->data->get_computer_currentimage_name(); - my $current_image_name = $status->{currentimage}; - my $vcld_post_load_status = $self->os->get_post_load_status(); - - if (!$current_image_revision_id) { - notify($ERRORS{'OK'}, 0, "unable to retrieve image name from currentimage.txt on VM $computer_name, returning 'RELOAD'"); - return $status; - } - elsif ($current_image_revision_id eq $imagerevision_id) { - notify($ERRORS{'OK'}, 0, "currentimage.txt image $current_image_revision_id ($current_image_name) matches requested imagerevision_id $imagerevision_id on VM $computer_name"); - $status->{image_match} = 1; - } - else { - notify($ERRORS{'OK'}, 0, "currentimage.txt imagerevision_id $current_image_revision_id ($current_image_name) does not match requested imagerevision_id $imagerevision_id on VM $computer_name, returning 'RELOAD'"); - return $status; - } - - - # Determine the overall machine status based on the individual status results - if ($status->{ssh} && $status->{image_match}) { - $status->{status} = 'READY'; - } - else { - $status->{status} = 'RELOAD'; - } - - notify($ERRORS{'DEBUG'}, 0, "status set to $status->{status}"); - - - if ($request_forimaging) { - $status->{status} = 'RELOAD'; - notify($ERRORS{'OK'}, 0, "request_forimaging set, setting status to RELOAD"); - } - - if ($vcld_post_load_status) { - notify($ERRORS{'DEBUG'}, 0, "OS module post_load tasks have been completed on VM $computer_name"); - $status->{status} = 'READY'; - } - else { - notify($ERRORS{'OK'}, 0, "OS module post_load tasks have not been completed on VM $computer_name, returning 'POST_LOAD'"); - $status->{status} = 'POST_LOAD'; - } - - notify($ERRORS{'DEBUG'}, 0, "returning node status hash reference (\$node_status->{status}=$status->{status})"); - return $status; - -} ## end sub node_status - - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _delete_os_computer_mapping - - Parameters : computer id - Returns : 1 or 0 - Description : delete match VCL computer id with OpenStack instance id - -=cut - -sub _delete_os_computer_mapping { - my $self = shift; - my $computer_id = $self->data->get_computer_id(); - if (!defined($computer_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get computer id"); - return 0; - } - - my $sql_statement = <<EOF; -SELECT -computerid -FROM -openstackcomputermap -WHERE -computerid = '$computer_id' -EOF - - #notify($ERRORS{'DEBUG'}, 0, "delete_os_computer_mapping: $sql_statement"); - my @selected_rows = database_select($sql_statement); - if (scalar @selected_rows == 0) { - notify($ERRORS{'OK'}, 0, "no instance for $computer_id"); - return 1; - } - - $sql_statement = <<EOF; -DELETE FROM -openstackcomputermap -WHERE -computerid = '$computer_id' -EOF - #notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); - my $result = database_execute($sql_statement); - - if (!defined($result)) { - notify($ERRORS{'WARNING'}, 0, "failed to delete computer mapping"); - return 0; - } - - notify($ERRORS{'DEBUG'}, 0, "successfully deleted computer mapping"); - sleep 5; - return 1; -} ## end sub _delete_os_computer_mapping - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _get_os_flavor_id - - Parameters : image revision id - Returns : OpenStack image id or 0 - Description : match VCL image revision id with OpenStack image id - -=cut - -sub _get_os_flavor_id { - my $imagerevision_id = shift; - if (!defined($imagerevision_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get image revision id"); - return; - } - - my $sql_statement = <<EOF; -SELECT -flavordetails as flavor -FROM -openstackimagerevision -WHERE -imagerevisionid = '$imagerevision_id' -EOF - - #notify($ERRORS{'DEBUG'}, 0, "get_os_flavor_id: $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 $os_flavor_detail = from_json($selected_rows[0]{flavor}); - if (!defined($os_flavor_detail)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack flavor detail"); - return; - } - my $os_flavor_id = $os_flavor_detail->{flavor}{id}; - if (!defined($os_flavor_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack flavor id"); - return; - } - - notify($ERRORS{'DEBUG'}, 0, "os_flavor_id: $os_flavor_id"); - return $os_flavor_id; -} ## end sub _get_os_flavor_id - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _get_os_image_id - - Parameters : image revision id - Returns : OpenStack image id or 0 - Description : Get the OpenStack image id corresponding to the VCL image revision id - -=cut - -sub _get_os_image_id { - my $imagerevision_id = shift; - if (!defined($imagerevision_id)) { - notify($ERRORS{'DEBUG'}, 0, "failed to get image revision id"); - return 0; - } - - my $sql_statement = <<EOF; -SELECT -imagedetails as image -FROM -openstackimagerevision -WHERE -imagerevisionid = '$imagerevision_id' -EOF - - notify($ERRORS{'DEBUG'}, 0, "get_os_image_id: $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 0; - } - my $os_image_detail = from_json($selected_rows[0]{image}); - if (!defined($os_image_detail)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack image detail"); - return 0; - } - - my $os_image_id = $os_image_detail->{image}{id}; - if (!defined($os_image_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack image id"); - return 0; - } - - notify($ERRORS{'DEBUG'}, 0, "openstack image_id: $os_image_id"); - - return $os_image_id; -} ## end sub _get_os_image_id - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _get_os_instance_id - - Parameters : None - Returns : OpenStack instance id or 0 - Description : Checks the existence of an OpenStack instance. - -=cut -sub _get_os_instance_id { - my $self = shift; - my $computer_id = $self->data->get_computer_id(); - if (!defined($computer_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get computer id"); - return; - } - - my $sql_statement = <<EOF; -SELECT -instanceid as id -FROM -openstackcomputermap -WHERE -computerid = '$computer_id' -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 $os_instance_id = $selected_rows[0]{id}; - if (!defined($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack instance id"); - return; - } - - notify($ERRORS{'DEBUG'}, 0, "Openstack instance id for $computer_id is $os_instance_id"); - return $os_instance_id; -} - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _get_os_token_computer_url - - Parameters : None - Returns : Openstack auth (token, compute url) or 0 - Description : Get the OpenStack auth token and compute url - -=cut - -sub _get_os_token_compute_url { - my $self = shift; - - my $os_auth_url = $ENV{'OS_AUTH_URL'}; - my $os_tenant_name = $ENV{'OS_TENANT_NAME'}; - my $os_user_name = $ENV{'OS_USERNAME'}; - my $os_user_password = $ENV{'OS_PASSWORD'}; - my $os_service_name = $ENV{'OS_SERVICE_NAME'}; - if (!defined($os_auth_url) || !defined($os_tenant_name) - || !defined($os_user_name) || !defined($os_user_password) || !defined($os_service_name)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth information from environment"); - return 0; - } - - my $os_auth_data = { - auth => { - tenantName => $os_tenant_name, - passwordCredentials => { - username => $os_user_name, - password => $os_user_password, - } - } - }; - - my $ua = LWP::UserAgent->new(); - my $resp = $ua->post( - $os_auth_url . "/tokens", - content_type => 'application/json', - content => to_json($os_auth_data) - ); - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack token: " . 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 $os_token = $output->{access}{token}{id}; - if (!defined($os_token)) { - notify($ERRORS{'WARNING'}, 0, "failed to get token"); - return 0; - } - - my @serviceCatalog = @{ $output->{access}{serviceCatalog} }; - @serviceCatalog = grep { $_->{type} eq 'compute' } @serviceCatalog; - if (!@serviceCatalog) { - notify($ERRORS{'WARNING'}, 0, "failed to get compute service catalog"); - return 0; - } - - @serviceCatalog = grep { $_->{name} eq $os_service_name } @serviceCatalog; - my $serviceCatalog = $serviceCatalog[0]; - if (!defined($serviceCatalog)) { - notify($ERRORS{'WARNING'}, 0, "failed to get service name: $os_service_name"); - return 0; - } - - my $os_compute_url = $serviceCatalog->{endpoints}[0]{publicURL}; - if (!defined($os_compute_url)) { - notify($ERRORS{'WARNING'}, 0, "failed to get compute server url"); - return 0; - } - - #notify($ERRORS{'DEBUG'}, 0, "token: $os_token, compute_url: $os_compute_url"); - return ($os_token, $os_compute_url); -} ## end sub get_os_token_compute_url - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _insert_os_image_id - - Parameters : OpenStack image id - Returns : 1 or 0 - Description : insert OpenStack image id and corresponding imagerevision id - -=cut - -sub _insert_os_image_id { - my $self = shift; - my ($os_image_id, $os_flavor_id) = @_; - notify($ERRORS{'DEBUG'}, 0, "the openstack id: $os_image_id, flavor id: $os_flavor_id"); - if (!defined($os_image_id) || !defined($os_flavor_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack id: $os_image_id or flavor id: $os_flavor_id"); - return 0; - } - my $imagerevision_id = $self->data->get_imagerevision_id(); - if (!defined($imagerevision_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the imagerevision id"); - return 0; - } - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack auth info"); - return 0; - } - - my $ua = LWP::UserAgent->new(); - my $res = $ua->get( - $os_compute_url . "/images/" . $os_image_id, - x_auth_token => $os_token, - x_auth_project_id => $os_project_id, - ); - if (!$res->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack image info: " . join("\n", $res->content)); - return 0; - } - my $os_image_details = $res->content; - if (!defined($os_image_details)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); - return 0; - } - - my $resp = $ua->get( - $os_compute_url . "/flavors/". $os_flavor_id, - x_auth_token => $os_token, - x_auth_project_id => $os_project_id, - ); - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack flavor info: " . join("\n", $resp->content)); - return 0; - } - my $os_flavor_details = $resp->content; - if (!defined($os_flavor_details)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); - return 0; - } - - my $sql_statement = <<EOF; -INSERT INTO -openstackimagerevision ( -imagerevisionid, -imagedetails, -flavordetails) -VALUES ( - '$imagerevision_id', - '$os_image_details', - '$os_flavor_details') -EOF - - #notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); - my $result = database_execute($sql_statement); - - if (!defined($result)) { - notify($ERRORS{'WARNING'}, 0, "failed to insert openstack image id"); - return 0; - } - - notify($ERRORS{'DEBUG'}, 0, "successfully insert openstack image id"); - sleep 5; - return 1; -} ## end sub _insert_os_image_id - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _insert_os_instance_id - - Parameters : OpenStack instance id - Returns : 1 or 0 - Description : insert OpenStack instance id and corresponding computer id - -=cut - -sub _insert_os_instance_id { - my $self = shift; - my $os_instance_id = shift; - my $computer_id = $self->data->get_computer_id(); - if (!defined($os_instance_id) || !defined($computer_id)) { - notify($ERRORS{'DEBUG'}, 0, "failed to get the openstack instance id: $os_instance_id or computer id: $computer_id"); - return 0; - } - - my $sql_statement = <<EOF; -INSERT INTO -openstackcomputermap ( -instanceid, -computerid) -VALUES -('$os_instance_id', '$computer_id') -EOF - - #notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); - my $result = database_execute($sql_statement); - if (!defined($result)) { - notify($ERRORS{'WARNING'}, 0, "failed to insert openstack instance id"); - return 0; - } - - notify($ERRORS{'DEBUG'}, 0, "successfully insert openstack instance id and comptuer id"); - sleep 5; - return 1; -} ## end sub_insert_os_instance_id - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _post_os_create_image - - Parameters : OpenStack instance id - Returns : 1 or 0 - Description : capture OpenStack instance - -=cut - -sub _post_os_create_image{ - my $self = shift; - my $os_instance_id = shift; - if (!defined($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack instance id"); - return; - } - notify($ERRORS{'DEBUG'}, 0, "os_instance_id: $os_instance_id in sub _post_os_create_image"); - my $image_name = $self->data->get_image_name(); - if (!defined($image_name)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth information from environment"); - return; - } - notify($ERRORS{'DEBUG'}, 0, "os_image_name: $image_name in sub _post_os_create_image"); - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth information from environment"); - return; - } - - my $ua = LWP::UserAgent->new(); - my $server_data = { - createImage => { - name => $image_name, - } - }; - - my $res = $ua->post( - $os_compute_url . "/servers/" . $os_instance_id . "/action", - content_type => 'application/json', - x_auth_token => $os_token, - content => to_json($server_data) - ); - - if (!$res->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to execute capture image: " . join("\n", $res->content)); - return; - } - - my $resp = $ua->get( - $os_compute_url . "/images/detail", - server => $os_instance_id, - content_type => 'application/json', - x_auth_project_id => $os_project_id, - x_auth_token => $os_token - ); - - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to get image info: " . join("\n", $resp->content)); - return; - } - - my $output = from_json($resp->content); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); - return; - } - my $os_image_id = $output->{images}[0]{id}; - if (!defined($os_image_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to capture instance of $os_instance_id"); - return; - } - - notify($ERRORS{'DEBUG'}, 0, "openstack image id for caputed instance of $os_instance_id is $os_image_id"); - return $os_image_id; -} ## end sub _post_os_create_image - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _post_os_create_instance - - Parameters : None - Returns : 1 or 0 - Description : create an OpenStack instance - -=cut - -sub _post_os_create_instance { - my $self = shift; - - my $imagerevision_id = $self->data->get_imagerevision_id() || return; - my $computer_name = $self->data->get_computer_short_name() || return; - my $image_os_type = $self->data->get_image_os_type() || return; - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - my $os_key_name = $ENV{'VCL_LINUX_KEY'}; - if (!defined($os_project_id) || !defined($os_key_name)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack project id or key name"); - return; - } - if ($image_os_type eq 'linux') { - $os_key_name = $ENV{'VCL_LINUX_KEY'}; - notify($ERRORS{'OK'}, 0, "The $os_key_name is the key for Linux (default)"); - } - elsif ($image_os_type eq 'windows') { - $os_key_name = $ENV{'VCL_WINDOWS_KEY'}; - notify($ERRORS{'OK'}, 0, "The $os_key_name is the key for Windows"); - } - - my $os_image_id = _get_os_image_id($imagerevision_id); - if (!defined($os_image_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack image id"); - return; - } - - my $os_flavor_id = _get_os_flavor_id($imagerevision_id); - if (!$os_flavor_id) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack flavor id"); - return; - } - - my $ua = LWP::UserAgent->new(); - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $server_data = { - server => { - name => $computer_name, - imageRef => $os_image_id, - key_name => $os_key_name, - flavorRef => $os_flavor_id - } - }; - - my $resp = $ua->post( - $os_compute_url . "/servers", - content_type => 'application/json', - x_auth_token => $os_token, - x_auth_project_id => $os_project_id, - content => to_json($server_data) - ); - - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to execute run instance: " . join("\n", $resp->content)); - return; - } - - my $output = from_json($resp->content); - notify($ERRORS{'DEBUG'}, 0, "create_instance output: $output"); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); - return; - } - my $os_instance_id = $output->{'server'}{'id'}; - if (!defined($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to execute command to get the instance id on $computer_name"); - return; - } - - if (!$self->_insert_os_instance_id($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to insert the instance id : $os_instance_id"); - return; - } - - notify($ERRORS{'DEBUG'}, 0, "The create_instance: $os_instance_id\n"); - return $os_instance_id; -} ## end sub _post_os_create_instance - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _prepare_capture - - Parameters : None - Returns : 1 or 0 - Description : prepare capturing instance - -=cut - -sub _prepare_capture { - my $self = shift; - - my ($package, $filename, $line, $sub) = caller(0); - my $request_data = $self->data->get_request_data(); - if (!$request_data) { - notify($ERRORS{'WARNING'}, 0, "unable to retrieve request data hash"); - return 0; - } - my $computer_name = $self->data->get_computer_short_name(); - if (!defined($computer_name)) { - notify($ERRORS{'WARNING'}, 0, "failed to get computer name"); - return 0; - } - - if (!$self->data->set_imagemeta_sysprep(0)) { - notify($ERRORS{'WARNING'}, 0, "failed to set the imagemeta Sysprep value to 0"); - return 0; - } - - if ($self->os->can("pre_capture")) { - notify($ERRORS{'OK'}, 0, "calling OS module's pre_capture() subroutine"); - - if (!$self->os->pre_capture({end_state => 'on'})) { - notify($ERRORS{'WARNING'}, 0, "OS module pre_capture() failed"); - return 0; - } - } - - notify($ERRORS{'DEBUG'}, 0, "pre_capture() is done"); - return 1; -} ## end sub _prepare_capture - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _set_os_auth_conf - - Parameters : None - Returns : 1(success) or 0(failure) - Description : load openstack environment profile and set global environemnt variables - -example: openstack.conf -"os_tenant_name" => "admin", -"os_username" => "admin", -"os_password" => "adminpassword", -"os_auth_url" => "http://openstack_nova_url:5000/v2.0", -"os_service_name" => "nova", -"vcl_windows_key" => "vcl_windows_key", -"vcl_linux_key" => "vcl_linux_key", - -=cut - -sub _set_os_auth_conf { - my $self = shift; - # User's environment file - my $user_config_file = '/etc/vcl/openstack/openstack.conf'; - my %config = do($user_config_file); - if (!%config) { - notify($ERRORS{'WARNING'},0, "failure to process $user_config_file"); - return; - } - $self->{config} = \%config; - my $os_auth_url = $self->{config}->{os_auth_url}; - my $os_service_name = $self->{config}->{os_service_name}; - my $os_project_id = $self->{config}->{os_project_id}; - my $os_tenant_name = $self->{config}->{os_tenant_name}; - my $os_username = $self->{config}->{os_username}; - my $os_password = $self->{config}->{os_password}; - my $vcl_windows_key = $self->{config}->{vcl_windows_key}; - my $vcl_linux_key = $self->{config}->{vcl_linux_key}; - - # Set Environment File - $ENV{'OS_AUTH_URL'} = $os_auth_url; - $ENV{'OS_SERVICE_NAME'} = $os_service_name; - $ENV{'OS_PROJECT_ID'} = $os_project_id; - $ENV{'OS_TENANT_NAME'} = $os_tenant_name; - $ENV{'OS_USERNAME'} = $os_username; - $ENV{'OS_PASSWORD'} = $os_password; - $ENV{'VCL_WINDOWS_KEY'} = $vcl_windows_key; - $ENV{'VCL_LINUX_KEY'} = $vcl_linux_key; - - return 1; -}# end sub _set_os_auth_conf - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _terminate_os_instance - - Parameters : None - Returns : 1 or 0 - Description : terminate an OpenStack instance - -=cut - -sub _terminate_os_instance { - my $self = shift; - - my $computer_name = $self->data->get_computer_short_name() || return 0; - - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - my $os_instance_id = $self->_get_os_instance_id(); - if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id) || !defined($os_instance_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack auth info"); - return 0; - } - - my $ua = LWP::UserAgent->new(); - my $resp = $ua->delete( - $os_compute_url . "/servers/" . $os_instance_id, - x_auth_token => $os_token - ); - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to execute terminate instance: " . join("\n", $resp->content)); - return 0; - } - - sleep 30; - return 1; -} ## end sub _terminate_os_instance - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _update_os_instance - - Parameters : OpenStack instance id - Returns : 1 or 0 - Description : update the private ip address of the OpenStack instance in database - -=cut - -sub _update_private_ip { - my $self = shift; - - my $os_instance_id = shift; - my $computer_id = $self->data->get_computer_id() || return 0; - my $computer_name = $self->data->get_computer_short_name() || return 0; - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - if (!defined($os_instance_id) || !defined($os_token) || !defined($os_compute_url)) { - notify($ERRORS{'WARNING'}, 0, "failed to get the openstack auth info"); - return 0; - } - my $ua = LWP::UserAgent->new(); - my ($private_ip, $output, $resp); - my $main_loop = 60; - - # Find the correct instance among running instances using the private IP - while ($main_loop > 0) { - notify($ERRORS{'DEBUG'}, 0, "try to fetch the private IP address for $computer_name, loop of $main_loop"); - $resp = $ua->get( - $os_compute_url . "/servers/" . $os_instance_id, - x_auth_token => $os_token - ); - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to execute instance detail: " . join("\n", $resp->content)); - return 0; - } - - $output = from_json($resp->content); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); - return 0; - } - $private_ip = $output->{'server'}{'addresses'}{'private'}[0]->{'addr'}; - - if (defined($private_ip)) { - my $result = update_computer_private_ip_address($computer_id, $private_ip); - if (!defined($result)) { - notify($ERRORS{'WARNING'}, 0, "The $private_ip on Computer $computer_name is NOT updated"); - return 0; - } - notify($ERRORS{'DEBUG'}, 0, "private IP address is $private_ip for $computer_name"); - sleep 10; - return 1; - } - else { - notify($ERRORS{'DEBUG'}, 0, "waiting for assinging private IP address to $computer_name"); - } - - sleep 20; - $main_loop--; - } - - notify($ERRORS{'DEBUG'}, 0, "private IP address for $computer_name is not determined"); - return 0; -} ## end sub _update_private_ip - -#////////////////////////////////////////////////////////////////////////////// - -=head2 _wait_for_copying_image - - Parameters : OpenStack image id - Returns : 1 or 0 - Description : wait for copying the OpenStack image to repository - -=cut - -sub _wait_for_copying_image { - my $self = shift; - - my $os_image_id = shift; - my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); - my $os_project_id = $ENV{'OS_PROJECT_ID'}; - if (!defined($os_image_id) || !defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { - notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth info or image id: $os_image_id"); - return 0; - } - my $ua = LWP::UserAgent->new(); - my ($resp, $output, $image_status); - - my $main_loop = 100; - while ($main_loop > 0) { - $resp = $ua->get( - $os_compute_url . "/images/" . $os_image_id, - content_type => 'application/json', - x_auth_project_id => $os_project_id, - x_auth_token => $os_token - ); - - if (!$resp->is_success) { - notify($ERRORS{'WARNING'}, 0, "failed to get image info: " . join("\n", $resp->content)); - return 0; - } - - $output = from_json($resp->content); - $image_status = $output->{'image'}{'status'}; - if (defined($image_status)) { - notify($ERRORS{'DEBUG'}, 0, "image status: $image_status for loop #$main_loop"); - if ($image_status eq 'ACTIVE') { - notify($ERRORS{'OK'}, 0, "$os_image_id is available now"); - return 1; - } - elsif ($image_status eq 'SAVING') { - notify($ERRORS{'DEBUG'}, 0, "wait 25 seconds for capturing instance"); - sleep 25; - } - else { - notify($ERRORS{'DEBUG'}, 0, "failed to capture image for $os_image_id"); - return 0; - } - } - sleep 10; - $main_loop--; - } - - return 0; -} ## end sub _wait_for_copying_image - -sub power_reset { - - return 1; - -} - -#////////////////////////////////////////////////////////////////////////////// -1; -__END__ +#!/usr/bin/perl -w +############################################################################### +# $Id: openstack.pm +############################################################################### +# 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::openstack - VCL module to support the Openstack provisioning engine with REST APIs v2 + +=head1 SYNOPSIS + + Needs to be written + +=head1 DESCRIPTION + +This module provides VCL support for Openstack + +=cut + +############################################################################### +package VCL::Module::Provisioning::openstack; + +# Include File Copying for Perl +use File::Copy; + +# 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 IO::File; +use Fcntl qw(:DEFAULT :flock); +use File::Temp qw(tempfile); +use List::Util qw(max); +use VCL::utils; +use JSON qw(from_json to_json); +use LWP::UserAgent; + +#////////////////////////////////////////////////////////////////////////////// + +=head2 initialize + + Parameters : + Returns : + Description : + +=cut + +sub initialize { + my $self = shift; + notify($ERRORS{'DEBUG'}, 0, "OpenStack module initialized"); + + if ($self->_set_os_auth_conf()) { + notify($ERRORS{'OK'}, 0, "successfully set openStack auth configuration"); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to set openstack auth configuration"); + return 0; + } + + return 1; +} ## end sub initialize + + +#////////////////////////////////////////////////////////////////////////////// + +=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) !~ /openstack/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $computer_name = $self->data->get_computer_short_name() || return; + my $vmhost_name = $self->data->get_vmhost_short_name() || return; + my $computer_private_ip_address = $self->data->get_computer_private_ip_address(); + + # Remove existing VMs which were created for the reservation computer + if (_pingnode($computer_private_ip_address)) { + if (!$self->_terminate_os_instance()) { + notify($ERRORS{'WARNING'}, 0, "failed to delete VM $computer_name on VM host $vmhost_name"); + return 0; + } + } + # Remove existing openstack id for computer mapping in database + # Althought the instance is not pingable (delete it accidently), it should delete the instance from database + if (!$self->_delete_os_computer_mapping()) { + notify($ERRORS{'WARNING'}, 0, "failed to delete the openstack instance id from openstackcomputermap"); + return 0; + } + + return 1; + +} + +#////////////////////////////////////////////////////////////////////////////// + +=head2 provision + + Parameters : hash + Returns : 1(success) or 0(failure) + Description : loads virtual machine with requested image + +=cut + +sub load { + my $self = shift; + if (ref($self) !~ /openstack/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $reservation_id = $self->data->get_reservation_id() || return; + my $computer_id = $self->data->get_computer_id() || return; + my $computer_name = $self->data->get_computer_short_name() || return; + my $image_name = $self->data->get_image_name() || return; + my $vmhost_name = $self->data->get_vmhost_short_name() || return; + my $computer_private_ip_address = $self->data->get_computer_private_ip_address() || return; + + insertloadlog($reservation_id, $computer_id, "startload", "$computer_name $image_name"); + notify($ERRORS{'DEBUG'}, 0, "computer_private_ip_address = [$computer_private_ip_address]"); + + # Remove existing VMs which were created for the reservation computer + if (_pingnode($computer_private_ip_address)) { + if (!$self->_terminate_os_instance()) { + notify($ERRORS{'CRITICAL'}, 0, "failed to delete VM $computer_name on VM host $vmhost_name"); + } + } + # Remove existing openstack id for computer mapping in database + # Althought the instance is not pingable (delete it accidently), it should delete the instance from database + if (!$self->_delete_os_computer_mapping()) { + notify($ERRORS{'WARNING'}, 0, "failed to delete the openstack instance id from openstackcomputermap"); + return; + } + + # Create new instance + my $os_instance_id = $self->_post_os_create_instance(); + if (!defined($os_instance_id)) { + notify($ERRORS{'CRITICAL'}, 0, "failed to create an instance for computer $computer_name on VM host: $vmhost_name"); + return; + } + + # Update the private ip of the instance in database + if (!$self->_update_private_ip($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to update private ip of the instance in database"); + return; + } + + # 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 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) !~ /openstack/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $reservation_id = $self->data->get_reservation_id() || return; + my $current_imagerevision_id = $self->os->get_current_imagerevision_id(); + my $computer_id = $self->data->get_computer_id() || return; + my $image_name = $self->data->get_image_name() || return; + my $computer_name = $self->data->get_computer_short_name() || return; + my $computer_private_ip_address = $self->data->get_computer_private_ip_address() || return; + + insertloadlog($reservation_id, $computer_id, "startcapture", "$computer_name $image_name"); + notify($ERRORS{'DEBUG'}, 0, "computer_private_ip_address = [$computer_private_ip_address]"); + + # Remove existing VMs which were created for the reservation computer + if (!_pingnode($computer_private_ip_address)) { + notify($ERRORS{'WARNING'}, 0, "unable to ping to $computer_name"); + return; + } + + my $os_instance_id = $self->_get_os_instance_id(); + if (!defined($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "unable to get instance id for $computer_name"); + return; + } + notify($ERRORS{'DEBUG'}, 0, "os_instance_id: $os_instance_id"); + + my $os_flavor_id = _get_os_flavor_id($current_imagerevision_id); + notify($ERRORS{'DEBUG'}, 0, "current imagerevision id is $current_imagerevision_id, flavor_id: $os_flavor_id"); + if (!defined($os_flavor_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get current openstack flavor id"); + return; + } + + if (!$self->_prepare_capture()) { + notify($ERRORS{'WARNING'}, 0, "failed to execute prepare_capture"); + return; + } + + my $os_image_id = $self->_post_os_create_image($os_instance_id); + if (!defined($os_image_id)) { + notify($ERRORS{'CRITICAL'}, 0, "failed to create image for $computer_name"); + return; + } + notify($ERRORS{'DEBUG'}, 0, "os_image_id: $os_image_id"); + + if (!$self->_wait_for_copying_image($os_image_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute _wait_for_copying_image for $os_image_id"); + return; + } + + # insert image details and flavor details, check status is ACTIVE before insert + if (!$self->_insert_os_image_id($os_image_id, $os_flavor_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to insert openstack image id"); + return; + } + notify($ERRORS{'DEBUG'}, 0, "capturing $os_instance_id into $os_image_id is done"); + + return 1; +} ## end sub capture + +#////////////////////////////////////////////////////////////////////////////// + +=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) !~ /openstack/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return 0; + } + + my $imagerevision_id = $self->data->get_imagerevision_id() || return 0; + my $image_name = $self->data->get_image_name() || return 0; + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth info"); + return 0; + } + + # Get the openstack image id for the corresponding VCL image revision id + my $os_image_id = _get_os_image_id($imagerevision_id); + if (!defined($os_image_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to acquire the openstack image id : $os_image_id"); + return 0; + } + + my $ua = LWP::UserAgent->new(); + my $resp = $ua->get( + $os_compute_url . "/images/" . $os_image_id, + x_auth_token => $os_token, + x_auth_project_id => $os_project_id, + ); + + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to execute post token: " . join("\n", $resp->content)); + return 0; + } + + my $output = from_json($resp->content); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json ouput: $output"); + return 0; + } + + my $image_status = $output->{image}{status}; + if (defined($image_status) && $image_status eq 'ACTIVE') { + notify($ERRORS{'OK'}, 0, "The openstack image for $image_name exists"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "The openstack image for $image_name does NOT exists"); + return 0; + } + +} ## end sub does_image_exist + +#////////////////////////////////////////////////////////////////////////////// + +=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) !~ /openstack/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Attempt to get the image name argument + my $image_name = shift; + my $imagerevision_id = $self->data->get_imagerevision_id() || return; + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth info"); + return; + } + my $os_image_id = _get_os_image_id($imagerevision_id); + if (!defined($os_image_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to acquire the openstack image id : $os_image_id for $image_name"); + return; + } + + my $ua = LWP::UserAgent->new(); + my $resp = $ua->get( + $os_compute_url . "/images/" . $os_image_id, + x_auth_token => $os_token, + x_auth_project_id => $os_project_id, + ); + + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to execute post token: " . join("\n", $resp->content)); + return; + } + + my $output = from_json($resp->content); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json ouput: $output"); + return; + } + + my $os_image_size_bytes = $output->{'image'}{'OS-EXT-IMG-SIZE:size'}; + if (!defined($os_image_size_bytes)) { + notify($ERRORS{'WARNING'}, 0, "The openstack image size for $image_name does NOT exists"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "os_image_size_bytes: $os_image_size_bytes for $image_name"); + return round($os_image_size_bytes / 1024 / 1024); +} ## end sub get_image_size + +#////////////////////////////////////////////////////////////////////////////// + +=head2 node_status + + Parameters : $computer_id or $hash->{computer}{id} (optional) + Returns : string -- 'READY', 'POST_LOAD', or 'RELOAD' + Description : Checks the status of a VM. 'READY' is returned if the VM is + accessible via SSH, and the OS module's post-load tasks have + run. 'POST_LOAD' is returned if the VM only needs to have + the OS module's post-load tasks run before it is ready. + 'RELOAD' is returned otherwise. + +=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::openstack'; + 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; + } + } + + my $reservation_id = $self->data->get_reservation_id(); + my $computer_name = $self->data->get_computer_node_name(); + my $image_name = $self->data->get_image_name(); + my $request_forimaging = $self->data->get_request_forimaging(); + my $imagerevision_id = $self->data->get_imagerevision_id(); + my $computer_private_ip_address = $self->data->get_computer_private_ip_address(); + + + notify($ERRORS{'DEBUG'}, 0, "attempting to check the status of computer $computer_name, image: $image_name"); + + # 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'; + + # Check if node is pingable and retrieve the power status if the reservation ID is 0 + # The reservation ID will be 0 is this subroutine was not called as an object method, but with a computer ID argument + # The reservation ID will be 0 when called from healthcheck.pm + # The reservation ID will be > 0 if called from a normal VCL reservation + # Skip the ping and power status checks for a normal reservation to speed things up + if (!$reservation_id) { + if (_pingnode($computer_private_ip_address)) { + notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is pingable"); + $status->{ping} = 1; + } + else { + notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is not pingable"); + $status->{ping} = 0; + } + + } + + notify($ERRORS{'DEBUG'}, 0, "Trying to ssh..."); + # Check if SSH is available + if ($self->os->is_ssh_responding()) { + notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is responding to SSH"); + $status->{ssh} = 1; + } + else { + notify($ERRORS{'OK'}, 0, "VM $computer_name is not responding to SSH, returning 'RELOAD'"); + $status->{status} = 'RELOAD'; + $status->{ssh} = 0; + + # Skip remaining checks if SSH isn't available + return $status; + } + + my $current_image_revision_id = $self->os->get_current_imagerevision_id(); + $status->{currentimagerevision_id} = $current_image_revision_id; + + $status->{currentimage} = $self->data->get_computer_currentimage_name(); + my $current_image_name = $status->{currentimage}; + my $vcld_post_load_status = $self->os->get_post_load_status(); + + if (!$current_image_revision_id) { + notify($ERRORS{'OK'}, 0, "unable to retrieve image name from currentimage.txt on VM $computer_name, returning 'RELOAD'"); + return $status; + } + elsif ($current_image_revision_id eq $imagerevision_id) { + notify($ERRORS{'OK'}, 0, "currentimage.txt image $current_image_revision_id ($current_image_name) matches requested imagerevision_id $imagerevision_id on VM $computer_name"); + $status->{image_match} = 1; + } + else { + notify($ERRORS{'OK'}, 0, "currentimage.txt imagerevision_id $current_image_revision_id ($current_image_name) does not match requested imagerevision_id $imagerevision_id on VM $computer_name, returning 'RELOAD'"); + return $status; + } + + + # Determine the overall machine status based on the individual status results + if ($status->{ssh} && $status->{image_match}) { + $status->{status} = 'READY'; + } + else { + $status->{status} = 'RELOAD'; + } + + notify($ERRORS{'DEBUG'}, 0, "status set to $status->{status}"); + + + if ($request_forimaging) { + $status->{status} = 'RELOAD'; + notify($ERRORS{'OK'}, 0, "request_forimaging set, setting status to RELOAD"); + } + + if ($vcld_post_load_status) { + notify($ERRORS{'DEBUG'}, 0, "OS module post_load tasks have been completed on VM $computer_name"); + $status->{status} = 'READY'; + } + else { + notify($ERRORS{'OK'}, 0, "OS module post_load tasks have not been completed on VM $computer_name, returning 'POST_LOAD'"); + $status->{status} = 'POST_LOAD'; + } + + notify($ERRORS{'DEBUG'}, 0, "returning node status hash reference (\$node_status->{status}=$status->{status})"); + return $status; + +} ## end sub node_status + + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _delete_os_computer_mapping + + Parameters : computer id + Returns : 1 or 0 + Description : delete match VCL computer id with OpenStack instance id + +=cut + +sub _delete_os_computer_mapping { + my $self = shift; + my $computer_id = $self->data->get_computer_id(); + if (!defined($computer_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get computer id"); + return 0; + } + + my $sql_statement = <<EOF; +SELECT +computerid +FROM +openstackcomputermap +WHERE +computerid = '$computer_id' +EOF + + #notify($ERRORS{'DEBUG'}, 0, "delete_os_computer_mapping: $sql_statement"); + my @selected_rows = database_select($sql_statement); + if (scalar @selected_rows == 0) { + notify($ERRORS{'OK'}, 0, "no instance for $computer_id"); + return 1; + } + + $sql_statement = <<EOF; +DELETE FROM +openstackcomputermap +WHERE +computerid = '$computer_id' +EOF + #notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); + my $result = database_execute($sql_statement); + + if (!defined($result)) { + notify($ERRORS{'WARNING'}, 0, "failed to delete computer mapping"); + return 0; + } + + notify($ERRORS{'DEBUG'}, 0, "successfully deleted computer mapping"); + sleep 5; + return 1; +} ## end sub _delete_os_computer_mapping + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _get_os_flavor_id + + Parameters : image revision id + Returns : OpenStack image id or 0 + Description : match VCL image revision id with OpenStack image id + +=cut + +sub _get_os_flavor_id { + my $imagerevision_id = shift; + if (!defined($imagerevision_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get image revision id"); + return; + } + + my $sql_statement = <<EOF; +SELECT +flavordetails as flavor +FROM +openstackimagerevision +WHERE +imagerevisionid = '$imagerevision_id' +EOF + + #notify($ERRORS{'DEBUG'}, 0, "get_os_flavor_id: $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 $os_flavor_detail = from_json($selected_rows[0]{flavor}); + if (!defined($os_flavor_detail)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack flavor detail"); + return; + } + my $os_flavor_id = $os_flavor_detail->{flavor}{id}; + if (!defined($os_flavor_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack flavor id"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "os_flavor_id: $os_flavor_id"); + return $os_flavor_id; +} ## end sub _get_os_flavor_id + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _get_os_image_id + + Parameters : image revision id + Returns : OpenStack image id or 0 + Description : Get the OpenStack image id corresponding to the VCL image revision id + +=cut + +sub _get_os_image_id { + my $imagerevision_id = shift; + if (!defined($imagerevision_id)) { + notify($ERRORS{'DEBUG'}, 0, "failed to get image revision id"); + return 0; + } + + my $sql_statement = <<EOF; +SELECT +imagedetails as image +FROM +openstackimagerevision +WHERE +imagerevisionid = '$imagerevision_id' +EOF + + notify($ERRORS{'DEBUG'}, 0, "get_os_image_id: $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 0; + } + my $os_image_detail = from_json($selected_rows[0]{image}); + if (!defined($os_image_detail)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack image detail"); + return 0; + } + + my $os_image_id = $os_image_detail->{image}{id}; + if (!defined($os_image_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack image id"); + return 0; + } + + notify($ERRORS{'DEBUG'}, 0, "openstack image_id: $os_image_id"); + + return $os_image_id; +} ## end sub _get_os_image_id + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _get_os_instance_id + + Parameters : None + Returns : OpenStack instance id or 0 + Description : Checks the existence of an OpenStack instance. + +=cut +sub _get_os_instance_id { + my $self = shift; + my $computer_id = $self->data->get_computer_id(); + if (!defined($computer_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get computer id"); + return; + } + + my $sql_statement = <<EOF; +SELECT +instanceid as id +FROM +openstackcomputermap +WHERE +computerid = '$computer_id' +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 $os_instance_id = $selected_rows[0]{id}; + if (!defined($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack instance id"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "Openstack instance id for $computer_id is $os_instance_id"); + return $os_instance_id; +} + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _get_os_token_computer_url + + Parameters : None + Returns : Openstack auth (token, compute url) or 0 + Description : Get the OpenStack auth token and compute url + +=cut + +sub _get_os_token_compute_url { + my $self = shift; + + my $os_auth_url = $ENV{'OS_AUTH_URL'}; + my $os_tenant_name = $ENV{'OS_TENANT_NAME'}; + my $os_user_name = $ENV{'OS_USERNAME'}; + my $os_user_password = $ENV{'OS_PASSWORD'}; + my $os_service_name = $ENV{'OS_SERVICE_NAME'}; + if (!defined($os_auth_url) || !defined($os_tenant_name) + || !defined($os_user_name) || !defined($os_user_password) || !defined($os_service_name)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth information from environment"); + return 0; + } + + my $os_auth_data = { + auth => { + tenantName => $os_tenant_name, + passwordCredentials => { + username => $os_user_name, + password => $os_user_password, + } + } + }; + + my $ua = LWP::UserAgent->new(); + my $resp = $ua->post( + $os_auth_url . "/tokens", + content_type => 'application/json', + content => to_json($os_auth_data) + ); + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack token: " . 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 $os_token = $output->{access}{token}{id}; + if (!defined($os_token)) { + notify($ERRORS{'WARNING'}, 0, "failed to get token"); + return 0; + } + + my @serviceCatalog = @{ $output->{access}{serviceCatalog} }; + @serviceCatalog = grep { $_->{type} eq 'compute' } @serviceCatalog; + if (!@serviceCatalog) { + notify($ERRORS{'WARNING'}, 0, "failed to get compute service catalog"); + return 0; + } + + @serviceCatalog = grep { $_->{name} eq $os_service_name } @serviceCatalog; + my $serviceCatalog = $serviceCatalog[0]; + if (!defined($serviceCatalog)) { + notify($ERRORS{'WARNING'}, 0, "failed to get service name: $os_service_name"); + return 0; + } + + my $os_compute_url = $serviceCatalog->{endpoints}[0]{publicURL}; + if (!defined($os_compute_url)) { + notify($ERRORS{'WARNING'}, 0, "failed to get compute server url"); + return 0; + } + + #notify($ERRORS{'DEBUG'}, 0, "token: $os_token, compute_url: $os_compute_url"); + return ($os_token, $os_compute_url); +} ## end sub get_os_token_compute_url + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _insert_os_image_id + + Parameters : OpenStack image id + Returns : 1 or 0 + Description : insert OpenStack image id and corresponding imagerevision id + +=cut + +sub _insert_os_image_id { + my $self = shift; + my ($os_image_id, $os_flavor_id) = @_; + notify($ERRORS{'DEBUG'}, 0, "the openstack id: $os_image_id, flavor id: $os_flavor_id"); + if (!defined($os_image_id) || !defined($os_flavor_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack id: $os_image_id or flavor id: $os_flavor_id"); + return 0; + } + my $imagerevision_id = $self->data->get_imagerevision_id(); + if (!defined($imagerevision_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the imagerevision id"); + return 0; + } + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack auth info"); + return 0; + } + + my $ua = LWP::UserAgent->new(); + my $res = $ua->get( + $os_compute_url . "/images/" . $os_image_id, + x_auth_token => $os_token, + x_auth_project_id => $os_project_id, + ); + if (!$res->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack image info: " . join("\n", $res->content)); + return 0; + } + my $os_image_details = $res->content; + if (!defined($os_image_details)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); + return 0; + } + + my $resp = $ua->get( + $os_compute_url . "/flavors/". $os_flavor_id, + x_auth_token => $os_token, + x_auth_project_id => $os_project_id, + ); + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack flavor info: " . join("\n", $resp->content)); + return 0; + } + my $os_flavor_details = $resp->content; + if (!defined($os_flavor_details)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); + return 0; + } + + my $sql_statement = <<EOF; +INSERT INTO +openstackimagerevision ( +imagerevisionid, +imagedetails, +flavordetails) +VALUES ( + '$imagerevision_id', + '$os_image_details', + '$os_flavor_details') +EOF + + #notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); + my $result = database_execute($sql_statement); + + if (!defined($result)) { + notify($ERRORS{'WARNING'}, 0, "failed to insert openstack image id"); + return 0; + } + + notify($ERRORS{'DEBUG'}, 0, "successfully insert openstack image id"); + sleep 5; + return 1; +} ## end sub _insert_os_image_id + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _insert_os_instance_id + + Parameters : OpenStack instance id + Returns : 1 or 0 + Description : insert OpenStack instance id and corresponding computer id + +=cut + +sub _insert_os_instance_id { + my $self = shift; + my $os_instance_id = shift; + my $computer_id = $self->data->get_computer_id(); + if (!defined($os_instance_id) || !defined($computer_id)) { + notify($ERRORS{'DEBUG'}, 0, "failed to get the openstack instance id: $os_instance_id or computer id: $computer_id"); + return 0; + } + + my $sql_statement = <<EOF; +INSERT INTO +openstackcomputermap ( +instanceid, +computerid) +VALUES +('$os_instance_id', '$computer_id') +EOF + + #notify($ERRORS{'DEBUG'}, 0, "$sql_statement"); + my $result = database_execute($sql_statement); + if (!defined($result)) { + notify($ERRORS{'WARNING'}, 0, "failed to insert openstack instance id"); + return 0; + } + + notify($ERRORS{'DEBUG'}, 0, "successfully insert openstack instance id and comptuer id"); + sleep 5; + return 1; +} ## end sub_insert_os_instance_id + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _post_os_create_image + + Parameters : OpenStack instance id + Returns : 1 or 0 + Description : capture OpenStack instance + +=cut + +sub _post_os_create_image{ + my $self = shift; + my $os_instance_id = shift; + if (!defined($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack instance id"); + return; + } + notify($ERRORS{'DEBUG'}, 0, "os_instance_id: $os_instance_id in sub _post_os_create_image"); + my $image_name = $self->data->get_image_name(); + if (!defined($image_name)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth information from environment"); + return; + } + notify($ERRORS{'DEBUG'}, 0, "os_image_name: $image_name in sub _post_os_create_image"); + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth information from environment"); + return; + } + + my $ua = LWP::UserAgent->new(); + my $server_data = { + createImage => { + name => $image_name, + } + }; + + my $res = $ua->post( + $os_compute_url . "/servers/" . $os_instance_id . "/action", + content_type => 'application/json', + x_auth_token => $os_token, + content => to_json($server_data) + ); + + if (!$res->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to execute capture image: " . join("\n", $res->content)); + return; + } + + my $resp = $ua->get( + $os_compute_url . "/images/detail", + server => $os_instance_id, + content_type => 'application/json', + x_auth_project_id => $os_project_id, + x_auth_token => $os_token + ); + + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to get image info: " . join("\n", $resp->content)); + return; + } + + my $output = from_json($resp->content); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); + return; + } + my $os_image_id = $output->{images}[0]{id}; + if (!defined($os_image_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to capture instance of $os_instance_id"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "openstack image id for caputed instance of $os_instance_id is $os_image_id"); + return $os_image_id; +} ## end sub _post_os_create_image + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _post_os_create_instance + + Parameters : None + Returns : 1 or 0 + Description : create an OpenStack instance + +=cut + +sub _post_os_create_instance { + my $self = shift; + + my $imagerevision_id = $self->data->get_imagerevision_id() || return; + my $computer_name = $self->data->get_computer_short_name() || return; + my $image_os_type = $self->data->get_image_os_type() || return; + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + my $os_key_name = $ENV{'VCL_LINUX_KEY'}; + if (!defined($os_project_id) || !defined($os_key_name)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack project id or key name"); + return; + } + if ($image_os_type eq 'linux') { + $os_key_name = $ENV{'VCL_LINUX_KEY'}; + notify($ERRORS{'OK'}, 0, "The $os_key_name is the key for Linux (default)"); + } + elsif ($image_os_type eq 'windows') { + $os_key_name = $ENV{'VCL_WINDOWS_KEY'}; + notify($ERRORS{'OK'}, 0, "The $os_key_name is the key for Windows"); + } + + my $os_image_id = _get_os_image_id($imagerevision_id); + if (!defined($os_image_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack image id"); + return; + } + + my $os_flavor_id = _get_os_flavor_id($imagerevision_id); + if (!$os_flavor_id) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack flavor id"); + return; + } + + my $ua = LWP::UserAgent->new(); + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $server_data = { + server => { + name => $computer_name, + imageRef => $os_image_id, + key_name => $os_key_name, + flavorRef => $os_flavor_id + } + }; + + my $resp = $ua->post( + $os_compute_url . "/servers", + content_type => 'application/json', + x_auth_token => $os_token, + x_auth_project_id => $os_project_id, + content => to_json($server_data) + ); + + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to execute run instance: " . join("\n", $resp->content)); + return; + } + + my $output = from_json($resp->content); + notify($ERRORS{'DEBUG'}, 0, "create_instance output: $output"); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); + return; + } + my $os_instance_id = $output->{'server'}{'id'}; + if (!defined($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to get the instance id on $computer_name"); + return; + } + + if (!$self->_insert_os_instance_id($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to insert the instance id : $os_instance_id"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "The create_instance: $os_instance_id\n"); + return $os_instance_id; +} ## end sub _post_os_create_instance + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _prepare_capture + + Parameters : None + Returns : 1 or 0 + Description : prepare capturing instance + +=cut + +sub _prepare_capture { + my $self = shift; + + my ($package, $filename, $line, $sub) = caller(0); + my $request_data = $self->data->get_request_data(); + if (!$request_data) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve request data hash"); + return 0; + } + my $computer_name = $self->data->get_computer_short_name(); + if (!defined($computer_name)) { + notify($ERRORS{'WARNING'}, 0, "failed to get computer name"); + return 0; + } + + if (!$self->data->set_imagemeta_sysprep(0)) { + notify($ERRORS{'WARNING'}, 0, "failed to set the imagemeta Sysprep value to 0"); + return 0; + } + + if ($self->os->can("pre_capture")) { + notify($ERRORS{'OK'}, 0, "calling OS module's pre_capture() subroutine"); + + if (!$self->os->pre_capture({end_state => 'on'})) { + notify($ERRORS{'WARNING'}, 0, "OS module pre_capture() failed"); + return 0; + } + } + + notify($ERRORS{'DEBUG'}, 0, "pre_capture() is done"); + return 1; +} ## end sub _prepare_capture + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _set_os_auth_conf + + Parameters : None + Returns : 1(success) or 0(failure) + Description : load openstack environment profile and set global environemnt variables + +example: openstack.conf +"os_tenant_name" => "admin", +"os_username" => "admin", +"os_password" => "adminpassword", +"os_auth_url" => "http://openstack_nova_url:5000/v2.0", +"os_service_name" => "nova", +"vcl_windows_key" => "vcl_windows_key", +"vcl_linux_key" => "vcl_linux_key", + +=cut + +sub _set_os_auth_conf { + my $self = shift; + # User's environment file + my $user_config_file = '/etc/vcl/openstack/openstack.conf'; + my %config = do($user_config_file); + if (!%config) { + notify($ERRORS{'WARNING'},0, "failure to process $user_config_file"); + return; + } + $self->{config} = \%config; + my $os_auth_url = $self->{config}->{os_auth_url}; + my $os_service_name = $self->{config}->{os_service_name}; + my $os_project_id = $self->{config}->{os_project_id}; + my $os_tenant_name = $self->{config}->{os_tenant_name}; + my $os_username = $self->{config}->{os_username}; + my $os_password = $self->{config}->{os_password}; + my $vcl_windows_key = $self->{config}->{vcl_windows_key}; + my $vcl_linux_key = $self->{config}->{vcl_linux_key}; + + # Set Environment File + $ENV{'OS_AUTH_URL'} = $os_auth_url; + $ENV{'OS_SERVICE_NAME'} = $os_service_name; + $ENV{'OS_PROJECT_ID'} = $os_project_id; + $ENV{'OS_TENANT_NAME'} = $os_tenant_name; + $ENV{'OS_USERNAME'} = $os_username; + $ENV{'OS_PASSWORD'} = $os_password; + $ENV{'VCL_WINDOWS_KEY'} = $vcl_windows_key; + $ENV{'VCL_LINUX_KEY'} = $vcl_linux_key; + + return 1; +}# end sub _set_os_auth_conf + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _terminate_os_instance + + Parameters : None + Returns : 1 or 0 + Description : terminate an OpenStack instance + +=cut + +sub _terminate_os_instance { + my $self = shift; + + my $computer_name = $self->data->get_computer_short_name() || return 0; + + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + my $os_instance_id = $self->_get_os_instance_id(); + if (!defined($os_token) || !defined($os_compute_url) || !defined($os_project_id) || !defined($os_instance_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack auth info"); + return 0; + } + + my $ua = LWP::UserAgent->new(); + my $resp = $ua->delete( + $os_compute_url . "/servers/" . $os_instance_id, + x_auth_token => $os_token + ); + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to execute terminate instance: " . join("\n", $resp->content)); + return 0; + } + + sleep 30; + return 1; +} ## end sub _terminate_os_instance + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _update_os_instance + + Parameters : OpenStack instance id + Returns : 1 or 0 + Description : update the private ip address of the OpenStack instance in database + +=cut + +sub _update_private_ip { + my $self = shift; + + my $os_instance_id = shift; + my $computer_id = $self->data->get_computer_id() || return 0; + my $computer_name = $self->data->get_computer_short_name() || return 0; + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + if (!defined($os_instance_id) || !defined($os_token) || !defined($os_compute_url)) { + notify($ERRORS{'WARNING'}, 0, "failed to get the openstack auth info"); + return 0; + } + my $ua = LWP::UserAgent->new(); + my ($private_ip, $output, $resp); + my $main_loop = 60; + + # Find the correct instance among running instances using the private IP + while ($main_loop > 0) { + notify($ERRORS{'DEBUG'}, 0, "try to fetch the private IP address for $computer_name, loop of $main_loop"); + $resp = $ua->get( + $os_compute_url . "/servers/" . $os_instance_id, + x_auth_token => $os_token + ); + if (!$resp->is_success) { + notify($ERRORS{'WARNING'}, 0, "failed to execute instance detail: " . join("\n", $resp->content)); + return 0; + } + + $output = from_json($resp->content); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to parse json output"); + return 0; + } + $private_ip = $output->{'server'}{'addresses'}{'private'}[0]->{'addr'}; + + if (defined($private_ip)) { + my $result = update_computer_private_ip_address($computer_id, $private_ip); + if (!defined($result)) { + notify($ERRORS{'WARNING'}, 0, "The $private_ip on Computer $computer_name is NOT updated"); + return 0; + } + notify($ERRORS{'DEBUG'}, 0, "private IP address is $private_ip for $computer_name"); + sleep 10; + return 1; + } + else { + notify($ERRORS{'DEBUG'}, 0, "waiting for assinging private IP address to $computer_name"); + } + + sleep 20; + $main_loop--; + } + + notify($ERRORS{'DEBUG'}, 0, "private IP address for $computer_name is not determined"); + return 0; +} ## end sub _update_private_ip + +#////////////////////////////////////////////////////////////////////////////// + +=head2 _wait_for_copying_image + + Parameters : OpenStack image id + Returns : 1 or 0 + Description : wait for copying the OpenStack image to repository + +=cut + +sub _wait_for_copying_image { + my $self = shift; + + my $os_image_id = shift; + my ($os_token, $os_compute_url) = $self->_get_os_token_compute_url(); + my $os_project_id = $ENV{'OS_PROJECT_ID'}; + if (!defined($os_image_id) || !defined($os_token) || !defined($os_compute_url) || !defined($os_project_id)) { + notify($ERRORS{'WARNING'}, 0, "failed to get openstack auth info or image id: $os_image_id"); + return 0; + } + my $ua = LWP::UserAgent->new();
[... 50 lines stripped ...]