Skip to content

Commit 34f500e

Browse files
authored
Hitachi API for vsphere-xcopy-volume-populator from tatsumir (#1602)
Hitachi Vantara Storage API support for storage offload volume populator --------- Signed-off-by: Ryosuke Tatsumi <[email protected]> Signed-off-by: tatsumir <[email protected]>
1 parent 6a8690f commit 34f500e

File tree

14 files changed

+859
-36
lines changed

14 files changed

+859
-36
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Build and Push Container Image
2+
3+
on:
4+
push:
5+
branches:
6+
- vsphere-xcopy-volume-populator-hal-api
7+
8+
jobs:
9+
build-and-push:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
packages: write
13+
contents: read
14+
15+
steps:
16+
- name: Checkout Repository
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Podman
20+
run: |
21+
sudo apt update
22+
sudo apt install -y podman
23+
24+
- name: Log in to GitHub Container Registry (GHCR)
25+
run: |
26+
echo "${{ secrets.GHCR_TOKEN }}" | podman login ghcr.io -u $GITHUB_ACTOR --password-stdin
27+
28+
- name: Build Container Image
29+
run: |
30+
make CONTAINER_CMD=$(which podman) build-vsphere-xcopy-volume-populator-image REGISTRY=ghcr.io REGISTRY_ORG=tatsumir
31+
32+
- name: Push Container Image
33+
run: |
34+
make CONTAINER_CMD=$(which podman) push-vsphere-xcopy-volume-populator-image REGISTRY=ghcr.io REGISTRY_ORG=tatsumir
35+

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ vendor/**/Dockerfile.vcsim
4040
vendor/**/Dockerfile.govc
4141

4242
cmd/vsphere-xcopy-volume-populator/vmkfstools-wrapper/vmkfstools-wrapper.vib
43+
.github/workflows/build-push-images-hal.yml
44+

cmd/vsphere-xcopy-volume-populator/internal/ontap/ontap.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ func (c *NetappClonner) UnMap(initatorGroup string, targetLUN populator.LUN, _ p
3131
return c.api.LunUnmap(context.TODO(), initatorGroup, targetLUN.Name)
3232
}
3333

34-
func (c *NetappClonner) EnsureClonnerIgroup(initiatorGroup string, clonnerIqn string) (populator.MappingContext, error) {
34+
func (c *NetappClonner) EnsureClonnerIgroup(initiatorGroup string, clonnerIqn []string) (populator.MappingContext, error) {
3535
// esxs needs "vmware" as the group protocol.
3636
err := c.api.IgroupCreate(context.Background(), initiatorGroup, "iscsi", "vmware")
3737
if err != nil {
3838
// TODO ignore if exists error? with ontap there is no error
3939
return nil, fmt.Errorf("failed adding igroup %w", err)
4040
}
41-
err = c.api.EnsureIgroupAdded(context.Background(), initiatorGroup, clonnerIqn)
41+
err = c.api.EnsureIgroupAdded(context.Background(), initiatorGroup, clonnerIqn[0])
4242
if err != nil {
4343
return nil, fmt.Errorf("failed adding host to igroup %w", err)
4444
}

cmd/vsphere-xcopy-volume-populator/internal/populator/mocks/storage_mock_client.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/vsphere-xcopy-volume-populator/internal/populator/populate.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,37 @@ type Populator interface {
1717
type LUN struct {
1818
//Name is the volume name or just name in the storage system
1919
Name string
20-
// target's IQN
21-
IQN string
22-
// the volume handle as set by the CSI driver field spec.volumeHandle
23-
VolumeHandle string
2420
// naa
2521
NAA string
2622
// SerialNumber is a representation of the disk. With combination of the
2723
// vendor ID it should ve globally unique and can be identified by udev, usually
2824
// under /dev/disk/by-id/ with some prefix or postfix, depending on the udev rule
2925
// and can also be found by lsblk -o name,serial
3026
SerialNumber string
27+
// target's IQN
28+
IQN string
29+
// Storage provider ID in hex
30+
ProviderID string
31+
// the volume handle as set by the CSI driver field spec.volumeHandle
32+
VolumeHandle string
33+
// Logical device ID of the volume
34+
LDeviceID string
35+
// Storage device Serial Number
36+
StorageSerialNumber string
37+
// Storage Protocol
38+
Protocol string
3139
}
3240

3341
// VMDisk is the target VMDisk in vmware
3442
type VMDisk struct {
3543
VMName string
3644
Datacenter string
3745
VmdkFile string
46+
VmnameDir string
3847
}
3948

4049
func (d *VMDisk) Path() string {
41-
return fmt.Sprintf("/vmfs/volumes/%s/%s/%s", d.Datacenter, d.VMName, d.VmdkFile)
50+
return fmt.Sprintf("/vmfs/volumes/%s/%s/%s", d.Datacenter, d.VmnameDir, d.VmdkFile)
4251
}
4352

4453
func ParseVmdkPath(vmdkPath string) (VMDisk, error) {
@@ -48,10 +57,14 @@ func ParseVmdkPath(vmdkPath string) (VMDisk, error) {
4857
}
4958
datastore := strings.TrimPrefix(parts[0], "[")
5059
pathParts := strings.SplitN(parts[1], "/", 2)
60+
5161
if len(pathParts) != 2 {
5262
return VMDisk{}, fmt.Errorf("Invalid vmdkPath %q, should be '[datastore] vmname/vmname.vmdk'", vmdkPath)
5363
}
54-
vmname := pathParts[0]
64+
65+
vmname_dir := pathParts[0]
5566
vmdk := pathParts[1]
56-
return VMDisk{VMName: vmname, Datacenter: datastore, VmdkFile: vmdk}, nil
67+
vmdkParts := strings.SplitN(vmdk, ".", 2)
68+
vmname_sub := vmdkParts[0]
69+
return VMDisk{VMName: vmname_sub, Datacenter: datastore, VmdkFile: vmdk, VmnameDir: vmname_dir}, nil
5770
}

cmd/vsphere-xcopy-volume-populator/internal/populator/remote_esxcli.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"math/big"
88
"slices"
9+
"strings"
910
"time"
1011

1112
"github.com/kubev2v/forklift/cmd/vsphere-xcopy-volume-populator/internal/vmware"
@@ -59,20 +60,46 @@ func (p *RemoteEsxcliPopulator) Populate(sourceVMDKFile string, volumeHandle str
5960
if err != nil {
6061
return err
6162
}
62-
esxIQN := ""
63+
uniqueUIDs := make(map[string]bool)
64+
hbaUIDs := []string{}
65+
66+
// Print the adapter information for debugging
67+
for i, val := range r {
68+
klog.Infof("Adapter [%d]: %+v", i, val)
69+
for key, field := range val {
70+
klog.Infof(" %s: %v", key, field)
71+
}
72+
}
73+
6374
for _, a := range r {
64-
// get the first adapter iqn
65-
iqnValue, ok := a["UID"]
66-
if !ok || len(iqnValue) == 0 {
67-
return fmt.Errorf("failed to extract the IQN from the adapter item%s", a)
75+
driver, hasDriver := a["Driver"]
76+
linkState, hasLink := a["LinkState"]
77+
uid, hasUID := a["UID"]
78+
79+
if !hasDriver || !hasLink || !hasUID || len(driver) == 0 || len(linkState) == 0 || len(uid) == 0 {
80+
continue
81+
}
82+
83+
drv := driver[0]
84+
link := linkState[0]
85+
id := uid[0]
86+
87+
// Check if the UID is FC, iSCSI or NVMe-oF
88+
isTargetUID := strings.HasPrefix(id, "fc.") || strings.HasPrefix(id, "iqn.") || strings.HasPrefix(id, "nqn.")
89+
90+
if link == "link-up" && isTargetUID {
91+
if _, exists := uniqueUIDs[id]; !exists {
92+
uniqueUIDs[id] = true
93+
hbaUIDs = append(hbaUIDs, id)
94+
klog.Infof("Storage Adapter UID: %s (Driver: %s)", id, drv)
95+
}
6896
}
69-
esxIQN = iqnValue[0]
70-
klog.Infof("iSCSI adapter IQN %s", esxIQN)
7197
}
7298

73-
mappingContext, err := p.StorageApi.EnsureClonnerIgroup(xcopyInitiatorGroup, esxIQN)
99+
mappingContext, err := p.StorageApi.EnsureClonnerIgroup(xcopyInitiatorGroup, hbaUIDs)
100+
74101
if err != nil {
75-
return fmt.Errorf("failed to add the ESX IQN %s to the initiator group %w", esxIQN, err)
102+
return fmt.Errorf("failed to add the ESX HBA UID %s to the initiator group %w", hbaUIDs, err)
76103
}
77104

78105
lun, err := p.StorageApi.ResolveVolumeHandleToLUN(volumeHandle)

cmd/vsphere-xcopy-volume-populator/internal/populator/storage.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type MappingContext map[string]any
1010

1111
type StorageMapper interface {
1212
// EnsureClonnerIgroup creates or updates an initiator group with the clonnerIqn
13-
EnsureClonnerIgroup(initiatorGroup string, clonnerIqn string) (MappingContext, error)
13+
EnsureClonnerIgroup(initiatorGroup string, clonnerIqn []string) (MappingContext, error)
1414
// Map is responsible to mapping an initiator group to a LUN
1515
Map(initatorGroup string, targetLUN LUN, context MappingContext) (LUN, error)
1616
// UnMap is responsible to unmapping an initiator group from a LUN

cmd/vsphere-xcopy-volume-populator/internal/primera3par/clonner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ func NewPrimera3ParClonner(storageHostname, storageUsername, storagePassword str
2121
}
2222

2323
// EnsureClonnerIgroup creates or update an initiator group with the clonnerIqn
24-
func (c *Primera3ParClonner) EnsureClonnerIgroup(initiatorGroup string, clonnerIqn string) (populator.MappingContext, error) {
25-
hostName, err := c.client.EnsureHostWithIqn(clonnerIqn)
24+
func (c *Primera3ParClonner) EnsureClonnerIgroup(initiatorGroup string, clonnerIqn []string) (populator.MappingContext, error) {
25+
hostName, err := c.client.EnsureHostWithIqn(clonnerIqn[0])
2626
if err != nil {
2727
return nil, fmt.Errorf("failed to ensure host with IQN: %w", err)
2828
}

cmd/vsphere-xcopy-volume-populator/internal/primera3par/par3_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestPrimera3ParClonner(t *testing.T) {
2020
mockClient.Volumes[targetLUN.Name] = targetLUN
2121

2222
t.Run("Ensure Clonner Igroup", func(t *testing.T) {
23-
_, err := clonner.EnsureClonnerIgroup(initiatorGroup, targetLUN.IQN)
23+
_, err := clonner.EnsureClonnerIgroup(initiatorGroup, []string{targetLUN.IQN})
2424
assert.NoError(t, err, "Expected no error when ensuring Clonner Igroup")
2525
_, hostExists := mockClient.Hosts["mock-host-"+targetLUN.IQN]
2626
assert.True(t, hostExists, "Expected host to exist")

0 commit comments

Comments
 (0)