Skip to content
Open
1 change: 1 addition & 0 deletions cmd/csi-driver/conform/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ if [ "$nodeService" = true ] || [ "$nodeInit" = true ]; then
ln -s /host/etc/multipath.conf /etc/multipath.conf
ln -s /host/etc/multipath /etc/multipath
ln -s /host/etc/iscsi /etc/iscsi
ln -s /host/etc/nvme /etc/nvme

# symlink to host os release files for parsing
if [ -f /host/etc/redhat-release ]; then
Expand Down
47 changes: 45 additions & 2 deletions cmd/csi-driver/conform/hpe-storage-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ if [ "$CONFORM_TO" = "ubuntu" ]; then
apt-get -qq install -y sg3-utils
exit_on_error $?
fi
# Install NVMe CLI tools
if [ ! -f /usr/sbin/nvme ]; then
apt-get -qq update
apt-get -qq install -y nvme-cli
exit_on_error $?
fi

elif [ "$CONFORM_TO" = "redhat" ]; then
# Install device-mapper-multipath
Expand Down Expand Up @@ -101,6 +107,12 @@ elif [ "$CONFORM_TO" = "redhat" ]; then
exit_on_error $?
fi

# Install NVMe CLI tools
if [ ! -f /usr/sbin/nvme ]; then
yum -y install nvme-cli
exit_on_error $?
fi

elif [ "$CONFORM_TO" = "sles" ]; then
# Install device-mapper-multipath
if [ ! -f /sbin/multipathd ]; then
Expand Down Expand Up @@ -128,13 +140,19 @@ elif [ "$CONFORM_TO" = "sles" ]; then
exit_on_error $?
fi

# Install NVMe CLI tools
if [ ! -f /usr/sbin/nvme ]; then
zypper -n install nvme-cli
exit_on_error $?
fi

elif [ "$CONFORM_TO" = "slem" ]; then
# SLE Micro
echo -n "Ensuring critical binaries are in the SLEM image: "
for bin in /usr/bin/sg_inq /sbin/mount.nfs4 /sbin/iscsid /sbin/multipathd; do
for bin in /usr/bin/sg_inq /sbin/mount.nfs4 /sbin/iscsid /sbin/multipathd /usr/sbin/nvme; do
echo -n "$bin "
if [ ! -f $bin ]; then
echo "$bin is missing. Run 'transactional-update -n pkg install multipath-tools open-iscsi nfs-client sg3_utils' on the worker nodes and reboot."
echo "$bin is missing. Run 'transactional-update -n pkg install multipath-tools open-iscsi nfs-client sg3_utils nvme-cli' on the worker nodes and reboot."
exit_on_error 1
fi
done
Expand All @@ -147,6 +165,12 @@ elif [ "$CONFORM_TO" = "coreos" ]; then
echo "Generating first-boot IQN"
echo "InitiatorName=$(iscsi-iname)" > /etc/iscsi/initiatorname.iscsi
fi
# Generate NVMe host NQN if it doesn't exist
if ! [[ -e /etc/nvme/hostnqn ]]; then
echo "Generating first-boot Host NQN"
mkdir -p /etc/nvme
echo "$(nvme gen-hostnqn)" > /etc/nvme/hostnqn
fi
else
echo "unsupported configuration for node package checks. os $os_name"
exit 1
Expand All @@ -161,6 +185,25 @@ fi
# Load iscsi_tcp modules, its a no-op if its already loaded
modprobe iscsi_tcp

# Load NVMe-oTCP modules
modprobe nvme-core
modprobe nvme-tcp


# Generate NVMe host NQN if it doesn't exist (for non-CoreOS systems)
if [ "$CONFORM_TO" != "coreos" ]; then
if ! [[ -e /etc/nvme/hostnqn ]]; then
echo "Generating Host NQN"
mkdir -p /etc/nvme
nvme gen-hostnqn > /etc/nvme/hostnqn
exit_on_error $?
fi
fi

# Ensure hostnqn file has proper permissions
if [[ -e /etc/nvme/hostnqn ]]; then
chmod 644 /etc/nvme/hostnqn
fi
# Don't let udev automatically scan targets(all luns) on Unit Attention.
# This will prevent udev scanning devices which we are attempting to remove
if [ -f /lib/udev/rules.d/90-scsi-ua.rules ]; then
Expand Down
1 change: 1 addition & 0 deletions pkg/driver/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
// Protocol types
iscsi = "iscsi"
fc = "fc"
nvmeotcp = "nvmeotcp"

// defaultFileSystem is the implemenation-specific default value
defaultFileSystem = "xfs"
Expand Down
35 changes: 29 additions & 6 deletions pkg/driver/controller_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,9 @@ func (driver *Driver) controllerPublishVolume(
requestedAccessProtocol = iscsi
} else if requestedAccessProtocol == "fc" {
requestedAccessProtocol = fc
}
} else if requestedAccessProtocol == "nvmetcp" {
requestedAccessProtocol = "nvmetcp"
}

if existingNode != nil {
log.Tracef("CSP has already been notified about the node with ID %s and UUID %s", existingNode.ID, existingNode.UUID)
Expand Down Expand Up @@ -957,12 +959,33 @@ func (driver *Driver) controllerPublishVolume(

// TODO: add any additional info necessary to mount the device
publishContext := map[string]string{}
publishContext[serialNumberKey] = publishInfo.SerialNumber
publishContext[accessProtocolKey] = publishInfo.AccessInfo.BlockDeviceAccessInfo.AccessProtocol
publishContext[targetNamesKey] = strings.Join(publishInfo.AccessInfo.BlockDeviceAccessInfo.TargetNames, ",")
publishContext[targetScopeKey] = requestedTargetScope
publishContext[lunIDKey] = strconv.Itoa(int(publishInfo.AccessInfo.BlockDeviceAccessInfo.LunID))

// NVMe over TCP support
if strings.EqualFold(publishInfo.AccessInfo.BlockDeviceAccessInfo.AccessProtocol, "nvmetcp") {
publishContext[serialNumberKey] = publishInfo.SerialNumber
publishContext[accessProtocolKey] = "nvmetcp"
// NQN, target address, and port for NVMe/TCP
if len(publishInfo.AccessInfo.BlockDeviceAccessInfo.TargetNames) > 0 {
publishContext[targetNamesKey] = publishInfo.AccessInfo.BlockDeviceAccessInfo.TargetNames[0]
}
if len(publishInfo.AccessInfo.BlockDeviceAccessInfo.NvmetcpAccessInfo.DiscoveryIPs) > 0 {
publishContext[discoveryIPsKey] = publishInfo.AccessInfo.BlockDeviceAccessInfo.NvmetcpAccessInfo.DiscoveryIPs[0]
}
if publishInfo.AccessInfo.BlockDeviceAccessInfo.NvmetcpAccessInfo.TargetPort != "" {
publishContext[targetPortKey] = publishInfo.AccessInfo.BlockDeviceAccessInfo.NvmetcpAccessInfo.TargetPort
} else {
publishContext[targetPortKey] = "4420" // default NVMe/TCP port
}

}else{
publishContext[serialNumberKey] = publishInfo.SerialNumber
publishContext[accessProtocolKey] = publishInfo.AccessInfo.BlockDeviceAccessInfo.AccessProtocol
publishContext[targetNamesKey] = strings.Join(publishInfo.AccessInfo.BlockDeviceAccessInfo.TargetNames, ",")
publishContext[targetScopeKey] = requestedTargetScope
publishContext[lunIDKey] = strconv.Itoa(int(publishInfo.AccessInfo.BlockDeviceAccessInfo.LunID))

}

// Start of population of target array details
if publishInfo.AccessInfo.BlockDeviceAccessInfo.SecondaryBackendDetails.PeerArrayDetails != nil {
secondaryArrayMarshalledStr, err := json.Marshal(&publishInfo.AccessInfo.BlockDeviceAccessInfo.SecondaryBackendDetails)
Expand Down
46 changes: 44 additions & 2 deletions pkg/driver/node_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ var (

const (
fileHostIPKey = "hostIP"
nvmetcp = "nvmetcp"
targetPortKey = "targetPort"
)

var isWatcherEnabled = false
Expand Down Expand Up @@ -573,6 +575,41 @@ func (driver *Driver) setupDevice(
log.Tracef(">>>>> setupDevice, volumeID: %s, publishContext: %v", volumeID, log.MapScrubber(publishContext))
defer log.Trace("<<<<< setupDevice")

// Handle NVMe over TCP volumes
if publishContext[accessProtocolKey] == "nvmetcp" {
volume := &model.Volume{
SerialNumber: publishContext[serialNumberKey],
AccessProtocol: publishContext[accessProtocolKey],
Nqn: publishContext[targetNamesKey], // NQN for NVMe
TargetAddress: publishContext[discoveryIPsKey], // Target IP
TargetPort: publishContext[targetPortKey], // Target port (default 4420)
ConnectionMode: defaultConnectionMode,
}

// Cleanup any stale device existing before stage
device, _ := driver.chapiDriver.GetDevice(volume)
if device != nil {
device.TargetScope = volume.TargetScope
err := driver.chapiDriver.DeleteDevice(device)
if err != nil {
log.Warnf("Failed to cleanup stale NVMe device %s before staging, err %s", device.AltFullPathName, err.Error())
}
}

// Create NVMe Device
devices, err := driver.chapiDriver.CreateDevices([]*model.Volume{volume})
if err != nil {
log.Errorf("Failed to create NVMe device from publish info. Error: %s", err.Error())
return nil, err
}
if len(devices) == 0 {
log.Errorf("Failed to get the NVMe device just created using the volume %+v", volume)
return nil, fmt.Errorf("unable to find the NVMe device for volume %+v", volume)
}

return devices[0], nil
}

// TODO: Enhance CHAPI to work with a PublishInfo object rather than a volume

discoveryIps := strings.Split(publishContext[discoveryIPsKey], ",")
Expand Down Expand Up @@ -2163,7 +2200,7 @@ func (driver *Driver) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoReque
watcher, _ := util.InitializeWatcher(getNodeInfoFunc)
// Add list of files /and directories to watch. The list contains
// iSCSI , FC and CHAP Info and Networking config directories
list := []string{"/etc/sysconfig/network-scripts/", "/etc/sysconfig/network/", "/etc/iscsi/initiatorname.iscsi", "/etc/networks", "/etc/iscsi/iscsid.conf"}
list := []string{"/etc/sysconfig/network-scripts/", "/etc/sysconfig/network/", "/etc/iscsi/initiatorname.iscsi", "/etc/networks", "/etc/iscsi/iscsid.conf", "/etc/nvme/hostnqn"}
watcher.AddWatchList(list)
// Start event the watcher in a separate thread.
go watcher.StartWatcher()
Expand Down Expand Up @@ -2222,18 +2259,22 @@ func (driver *Driver) nodeGetInfo() (string, error) {

var iqns []*string
var wwpns []*string
var nqns []*string
for _, initiator := range initiators {
if initiator.Type == iscsi {
for i := 0; i < len(initiator.Init); i++ {
iqns = append(iqns, &initiator.Init[i])
}
} else if initiator.Type == nvmeotcp {
for i := 0; i < len(initiator.Init); i++ {
nqns = append(nqns, &initiator.Init[i])
}
} else {
for i := 0; i < len(initiator.Init); i++ {
wwpns = append(wwpns, &initiator.Init[i])
}
}
}

var cidrNetworks []*string
for _, network := range networks {
log.Infof("Processing network named %s with IpV4 CIDR %s", network.Name, network.CidrNetwork)
Expand All @@ -2249,6 +2290,7 @@ func (driver *Driver) nodeGetInfo() (string, error) {
Iqns: iqns,
Networks: cidrNetworks,
Wwpns: wwpns,
Nqns: nqns,
}

nodeID, err := driver.flavor.LoadNodeInfo(node)
Expand Down
9 changes: 9 additions & 0 deletions pkg/driver/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,12 @@ func removeDataFile(dirPath string, fileName string) error {
func isValidIP(ip string) bool {
return ip != "" && net.ParseIP(ip) != nil
}
// GetNvmeInitiator returns the NVMe host NQN as a string
func GetNvmeInitiator() (string, error) {
data, err := ioutil.ReadFile("/etc/nvme/hostnqn")
if err != nil {
return "", err
}
nqn := strings.TrimSpace(string(data))
return nqn, nil
}
25 changes: 22 additions & 3 deletions pkg/flavor/kubernetes/flavor.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,18 @@ func (flavor *Flavor) LoadNodeInfo(node *model.Node) (string, error) {
nodeInfo.Spec.WWPNs = wwpnsFromNode
updateNodeRequired = true
}
nqnsFromNode := getNqnsFromNode(node)
if !reflect.DeepEqual(nodeInfo.Spec.NQNs, nqnsFromNode) {
nodeInfo.Spec.NQNs = nqnsFromNode
updateNodeRequired = true
}

if !updateNodeRequired {
// no update needed to existing CRD
return node.UUID, nil
}
log.Infof("updating Node %s with iqns %v wwpns %v networks %v",
nodeInfo.Name, nodeInfo.Spec.IQNs, nodeInfo.Spec.WWPNs, nodeInfo.Spec.Networks)
log.Infof("updating Node %s with iqns %v wwpns %v networks %v nqns %v",
nodeInfo.Name, nodeInfo.Spec.IQNs, nodeInfo.Spec.WWPNs, nodeInfo.Spec.Networks, nodeInfo.Spec.NQNs)
_, err := flavor.crdClient.StorageV1().HPENodeInfos().Update(nodeInfo)
if err != nil {
log.Errorf("Error updating the node %s - %s\n", nodeInfo.Name, err.Error())
Expand All @@ -262,6 +267,7 @@ func (flavor *Flavor) LoadNodeInfo(node *model.Node) (string, error) {
IQNs: getIqnsFromNode(node),
Networks: getNetworksFromNode(node),
WWPNs: getWwpnsFromNode(node),
NQNs: getNqnsFromNode(node),
},
}

Expand Down Expand Up @@ -303,6 +309,14 @@ func getNetworksFromNode(node *model.Node) []string {
return networks
}

func getNqnsFromNode(node *model.Node) []string {
var nqns []string
for i := 0; i < len(node.Nqns); i++ {
nqns = append(nqns, *node.Nqns[i])
}
return nqns
}

// UnloadNodeInfo remove the HPENodeInfo from the list of CRDs
func (flavor *Flavor) UnloadNodeInfo() {
log.Tracef(">>>>>> UnloadNodeInfo with name %s", flavor.nodeName)
Expand Down Expand Up @@ -353,14 +367,19 @@ func (flavor *Flavor) GetNodeInfo(nodeID string) (*model.Node, error) {
for i := range wwpns {
wwpns[i] = &nodeInfo.Spec.WWPNs[i]
}
nqns := make([]*string, len(nodeInfo.Spec.NQNs))
for i := range nqns {
nqns[i] = &nodeInfo.Spec.NQNs[i]
}
node := &model.Node{
Name: nodeInfo.ObjectMeta.Name,
UUID: nodeInfo.Spec.UUID,
Iqns: iqns,
Networks: networks,
Wwpns: wwpns,
Nqns: nqns,
}

log.Tracef("HPE Node Info sent to CSP: %v", node)
return node, nil
}
}
Expand Down
Loading