#!/usr/bin/env bash # Copyright (C) 2015 Hewlett-Packard Development Company, L.P. # Copyright (C) 2015 Pure Storage, Inc. # # Licensed 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. # Shell commands to get virsh the information it # needs to successfully pass through a Fibre Channel PCI Card to the virtual # machine this script is running on. The instance only knows its IP address, # while its Virsh name is required for pass through. This script uses Nova on # the provider blade as an intermediary to find the name. Meanwhile, this # script finds the Fibre Channel PCI card on the provider and generates the # information Virsh needs to attach it. # # Expect four env variables, the provider hostname (optionally user if needed) # the private key file we should use to connect to the provider, and the file # that should be sourced for OpenStack credentials. # # export FC_PROVIDER=my.provider.hostname # export FC_PROVIDER_USER=root # export FC_PROVIDER_KEY=/opt/nodepool-scripts/passthrough # export FC_PROVIDER_RC=/root/keystonerc_jenkins # # The maximum number of FC devices to passthrough, failing if they cannot all be # aquired # export FC_NUM=2 (default 1) # # For single node setups where the hypervisor is the same as the provider, and dns # is not configured, export this variable to use the provider ip as the hypervisor # export FC_SINGLE_NODE=1 FC_NUM=${FC_NUM:-1} FC_PCI_VAR_NAME=${FC_PCI_VAR_NAME:-"fc_pci_device"} echo "Planning to passthrough $FC_NUM pci devices" eth0_ip=$(hostname -I | cut -f1 -d' ') PROVIDER=${FC_PROVIDER} if [[ -z $PROVIDER ]]; then eth0_ip_base=$(echo $eth0_ip | cut -f1,2,3 -d.) PROVIDER="${eth0_ip_base}.1" fi PROVIDER_KEY=${FC_PROVIDER_KEY:-"/opt-nodepool-scripts/passthrough"} PROVIDER_RC=${FC_PROVIDER_RC:-"keystonerc_jenkins"} CURRENT_USER=$(whoami) PROVIDER_USER=${FC_PROVIDER_USER:-$CURRENT_USER} # Passthrough is a private key that needs to be setup for the provider # and any compute nodes that might end up hosting the VM we want passthrough on. # We will assume ownership of the key (probably as the jenkins user..), also # assuming the group is the same name as the user... sudo chown $CURRENT_USER:$CURRENT_USER $PROVIDER_KEY chmod 0400 $PROVIDER_KEY # Get our NOVA_ID NOVA_LIST=$(ssh -i $PROVIDER_KEY $PROVIDER_USER@$PROVIDER "source $PROVIDER_RC && nova list") nova_result=$? NOVA_ID=$(echo "$NOVA_LIST" | grep ACTIVE | grep -v deleting | grep $eth0_ip | cut -d \| -f 2 | tr -d '[:space:]') echo "NOVA_ID result: $nova_result" if [[ $nova_result -ne 0 || -z "$NOVA_ID" ]]; then echo "Unable to get Nova ID. Aborting. Debug info:" echo $NOVA_LIST echo "NOVA_ID: $NOVA_ID" exit 2 fi # Get instance details NOVA_DETAILS=$(ssh -i $PROVIDER_KEY $PROVIDER_USER@$PROVIDER "source $PROVIDER_RC && nova show $NOVA_ID") nova_results=$? # Get our Virsh name VIRSH_NAME=$(echo "$NOVA_DETAILS" | grep instance_name | cut -d \| -f 3 | tr -d '[:space:]') virsh_result=$? echo "VIRSH_NAME result: $virsh_result" if [[ $nova_result -ne 0 || $virsh_result -ne 0 || -z "$VIRSH_NAME" ]]; then echo "Unable to get Virsh Name. Aborting. Debug info:" echo "NOVA_LIST:" echo $NOVA_LIST echo "NOVA_DETAILS:" echo $NOVA_DETAILS echo "VIRSH_NAME: $VIRSH_NAME" exit 2 fi # Get the hypervisor_hostname if [[ -z $FC_SINGLE_NODE ]]; then HYPERVISOR=$(echo "$NOVA_DETAILS" | grep hypervisor_hostname | cut -d \| -f 3 | tr -d '[:space:]') hypervisor_result=$? echo "HYPERVISOR result: $hypervisor_result" if [[ $hypervisor_result -ne 0 || -z "$HYPERVISOR" ]]; then echo "Unable to get Hypervisor Host Name. Aborting. Debug info:" echo "NOVA_LIST:" echo $NOVA_LIST echo "NOVA_DETAILS:" echo $NOVA_DETAILS echo "HYPERVISOR: $HYPERVISOR" exit 2 fi else HYPERVISOR=$PROVIDER fi echo "Found Hypervisor hostname: $HYPERVISOR" fc_pci_device_cmd="echo \$$FC_PCI_VAR_NAME" fc_pci_device=$(ssh -i $PROVIDER_KEY $PROVIDER_USER@$HYPERVISOR "source /etc/profile; $fc_pci_device_cmd") if [[ -z $fc_pci_device ]]; then echo "No FC device known. Set fc_pci_device in your /etc/profile.d or /etc/environment (depending on distro and ssh configuration) to the desired 'Class Device path', e.g. '0000:21:00.2'" exit 2 fi echo "Found pci devices: $fc_pci_device" function is_device_online() { fc_device=$1 # If a device is not "Online" we'll get an empty # string as a result of the following command. cmd="systool -c fc_host -v" OUTPUT=$(ssh -i $PROVIDER_KEY $PROVIDER_USER@$HYPERVISOR "systool -c fc_host -v") ONLINE=`echo "$OUTPUT" | grep -B12 'Online' | grep 'Class Device path' | grep $fc_device` echo "online result='$ONLINE'" if [ -z "$ONLINE" ]; then return 0; else return 1; fi } exit_code=1 errexit=$(set +o | grep errexit) # Ignore errors set +e let num_attached=0 for pci in $fc_pci_device; do echo "Trying passthrough for $pci" BUS=$(echo $pci | cut -d : -f2) SLOT=$(echo $pci | cut -d : -f3 | cut -d . -f1) FUNCTION=$(echo $pci | cut -d : -f3 | cut -d . -f2) XML="
" echo $XML fcoe=`mktemp --suffix=_fcoe.xml` echo $XML > $fcoe fc_virsh_device="pci_0000_${BUS}_${SLOT}_${FUNCTION}" scp -i $PROVIDER_KEY $fcoe $PROVIDER_USER@$HYPERVISOR:/tmp/ # Run passthrough and clean up. # TODO: At the point where we can do more than one node on a provider we # will need to do this cleanup at the end of the job and not *before* attaching # since we won't know which ones are still in use echo $(sudo lspci | grep -i fib) ssh -i $PROVIDER_KEY $PROVIDER_USER@$HYPERVISOR "virsh nodedev-dettach $fc_virsh_device" detach_result=$? echo "Detach result: $detach_result" if [[ $detach_result -ne 0 ]]; then echo "Detach failed ($detach_result). Trying next device..." continue fi # Reattach the device to the host. # This will hopefully reset the device echo $(sudo lspci | grep -i fib) ssh -i $PROVIDER_KEY $PROVIDER_USER@$HYPERVISOR "virsh nodedev-reattach $fc_virsh_device" reattach_result=$? echo "reattach result: $reattach_result" if [[ $reattach_result -ne 0 ]]; then echo "Reattach failed ($reattach_result). Trying next device..." continue fi # Now that the device has been re-attached to it's host device driver # systool should be able to see it. Make sure it's online. is_device_online $pci online=$? if [ $online -eq 1 ]; then echo "Device($pci) is Online" else echo "Device($pci) is NOT Online" # It does no good to passthrough an HBA that isn't Online. # When an HBA goes into 'Linkdown' or 'Offline' mode, the # host typically needs to get rebooted. continue fi echo $(sudo lspci | grep -i fib) ssh -i $PROVIDER_KEY $PROVIDER_USER@$HYPERVISOR "virsh attach-device $VIRSH_NAME $fcoe" attach_result=$? echo "Attach result: $attach_result" if [[ $attach_result -eq 0 ]]; then echo "Attached succeed. Trying next device..." (( num_attached += 1 )) exit_code=0 fi echo $(sudo lspci | grep -i fib) echo $num_attached if [[ $num_attached -eq $FC_NUM ]]; then echo "Attached $num_attached devices. Stopping" break fi done $errexit if [[ $exit_code -ne 0 ]]; then echo "FC Passthrough failed. Aborting." exit $exit_code fi if [[ $num_attached -ne $FC_NUM ]]; then echo "FC requested $FC_NUM, but only attached $num_attached. Aborting." exit 1 fi # Make sure that really it worked... sudo modprobe lpfc echo $? sudo systool -c fc_host -v echo $? echo $(sudo lspci | grep -i fib) device_path=$(sudo systool -c fc_host -v | grep "Device path") if [[ ${#device_path} -eq 0 ]]; then echo "Failed to install FC Drivers. Aborting." exit 1 fi