managementnode/lib/VCL/Module/Provisioning/openstack.pm (943 lines of code) (raw):

#!/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.5.1'; # Specify the version of Perl to use use 5.008000; use strict; use warnings; use diagnostics; use English qw(-no_match_vars); use 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__