diff --git a/RC_MIGRATE.md b/RC_MIGRATE.md new file mode 100644 index 0000000000000000000000000000000000000000..8d7d505e1de99bae9261059d732ffa37b32caf2b --- /dev/null +++ b/RC_MIGRATE.md @@ -0,0 +1,63 @@ +# How to use Remote Copy to migrate between 3PARs + +## Move VM with persistent disk from 3PAR A to B + +We need two Image datastores, each pointing to different 3PAR: + +- Image_DS_A - 3PAR A +- Image_DS_B - 3PAR B + +For migration we need three System datastores: + +- System_DS_A - 3PAR A +- System_DS_RC_AB - 3PAR A and SEC_config pointed to 3PAR B +- System_DS_B - 3PAR B + +### Process of migration + +In susntone select Migrate (without any label) and modal opens. In the modal select same host and in advanced select +System_DS_RC_AB. VM will suspend for while, RC Group will be created and VM will be unsuspended. Now we have VM in HA +mode, because it is running with Peer Persistance and it has two groups of paths in multipath. + +We can check RCG state in SSMC, and when volume(s) is fully synchronized we can proceed to next step. + +**!!! IMPORTANT !!! Volume have to be fully synchronized.** + +In sustone select again same dialog, select same host but for datastore select System_DS_B. VM goes to suspend state, +RC Group will be removed and VM become unsuspended, but now running from 3PAR B. + +### Post tasks + +*TODO: This updates can be implemented into driver - tm/mv action.* + +OpenNebula doesn't known about this migration, so we have to edit at least three DB entities using onedb command: + +Update body of image(s) and disk(s) in VM, change image datastore ID and Name to Image_DS_B + +``` +onedb change-body image --id IMAGE_ID /IMAGE/DATASTORE_ID DS_ID_B +onedb change-body image --id IMAGE_ID /IMAGE/DATASTORE DS_NAME_B +onedb change-body vm --id VM_ID /VM/TEMPLATE/DISK/DATASTORE_ID DS_ID_B +onedb change-body vm --id VM_ID /VM/TEMPLATE/DISK/DATASTORE DS_NAME_B +``` + +Update list of images in both image datastores, remove image ID from Image_DS_A and add it to Image_DS_B +``` +onedb change-body datastore --id DS_ID_A /DATASTORE/IMAGES/ID[.=IMAGE_ID] --delete +# attention! following command only work properly with PR https://github.com/OpenNebula/one/pull/6170 +# do not run without this patch, otherwise it overwrites all other image IDs +onedb change-body datastore --id DS_ID_B /DATASTORE/IMAGES/ID IMAGE_ID --append +``` + +## Move VM with non-persistent disk from 3PAR A to B + +This is pretty similar to persistent version, but we skip step with migration to System_DS_RC_AB. This driver doesn't +support RC for non-persistent images, at least for now. We migrate VM directly to System_DS_B. Downtime will be longer, +because disk is copied by using DD utility during VM suspension. + +### Post tasks + +Image_DS_A can have RC enabled, which on background synchronizes all non-persistent (base) images by using RCG. After +all VMs using same particular disk are migrated, them we can update-body of each to point it to Image_DS_B, +update-body of image itself and update-body for both image datastore's to finalize move. We have to remove the image +from RCG in SSMC manually. \ No newline at end of file diff --git a/README.md b/README.md index 5387581425e084cdaa325ca58f7099764f333b2d..08ffd64bf54204a9e448ad29a61c57cd8ad69038 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,12 @@ More info: ## Compatibility This add-on is developed and tested with: -- OpenNebula 5.6 and 3PAR OS 3.2.2.612 (MU4)+P51,P56,P59,P94,P98,P102,P106,P113,P118,P127 -- OpenNebula 5.8 and 3PAR OS 3.3.1.410 (MU2)+P32,P34,P36,P37,P39,P40,P41,P42,P45,P48 +- OpenNebula 6.2 and 3PAR OS 3.3.1.648 (MU5)+P125,P126,P132,P135,P140,P146,P150,P151,P155,P156,P164,P170,P173 +- OpenNebula 5.10 and 3PAR OS 3.3.1.648 (MU5)+P125,P126,P132,P135,P140,P146,P150,P151 - OpenNebula 5.8 and 3PAR OS 3.3.1.460 (MU3)+P50,P58,P61,P77,P78,P81 +- OpenNebula 5.8 and 3PAR OS 3.3.1.410 (MU2)+P32,P34,P36,P37,P39,P40,P41,P42,P45,P48 +- OpenNebula 5.6 and 3PAR OS 3.2.2.612 (MU4)+P51,P56,P59,P94,P98,P102,P106,P113,P118,P127 + ## Requirements @@ -35,10 +38,9 @@ This add-on is developed and tested with: * Password-less SSH access from the front-end `oneadmin` user to the `node` instances. * 3PAR python package `python-3parclient` installed, WSAPI username, password and access to the 3PAR API network * libvirt-client package installed -* xmlstarlet package installed - used in TM monitor script instead of OpenNebula native ruby script because it is slow ```bash -yum install python-setuptools libvirt-client xmlstarlet +yum install python-setuptools libvirt-client easy_install pip pip install python-3parclient ``` @@ -51,11 +53,16 @@ pip install python-3parclient * sg3_utils package installed * `/etc/multipath.conf` need to have set `user_friendly_names no`, because we use WWNs instead of `mpathx` aliasses * `/etc/sudoers.d/opennebula` - add `ONE_3PAR` cmd alias +* `/etc/sudoers.d/opennebula-node-kvm` - add `ONE_3PAR` alias to the list ``` nano /etc/sudoers.d/opennebula ... -Cmnd_Alias ONE_3PAR = /sbin/multipath, /usr/sbin/multipathd, /sbin/dmsetup, /usr/sbin/blockdev, /usr/bin/tee /sys/block/*/device/delete, /usr/bin/rescan-scsi-bus.sh +Cmnd_Alias ONE_3PAR = /sbin/multipath, /usr/sbin/multipathd, /sbin/dmsetup, /usr/sbin/blockdev, /usr/bin/tee /sys/block/*/device/delete, /usr/bin/rescan-scsi-bus.sh, /sbin/mkswap, /usr/sbin/mkfs +... + +nano /etc/sudoers.d/opennebula-node-kmv +... oneadmin ALL=(ALL) NOPASSWD: ONE_MISC, ..., ONE_3PAR, ... ... ``` @@ -72,23 +79,28 @@ Support standard OpenNebula datastore operations: * SYSTEM datastore * TRIM/discard in the VM when virtio-scsi driver is in use (require `DEV_PREFIX=sd` and `DISCARD=unmap`) * disk images can be full provisioned, thin provisioned, thin deduplicated, thin compressed or thin deduplicated and compressed RAW block devices -* support different 3PAR CPGs as separate datastores +* support different 3PAR CPGs as separate datastore * support for 3PAR Priority Optimization Policy (QoS) * live VM snapshots * live VM migrations -* Volatile disks support (need patched KVM driver `attach_disk` script) +* volatile disks support (need patched KVM driver `attach_disk` script) +* support multiple storage systems +* support Remote Copy with Peer Persistence +* support Save As between storage systems +* support migrations of VMs between storage systems +* ds/clone operation support cloning image between storage systems * Sunstone integration - available via our enterprise repository ## Limitations 1. Tested only with KVM hypervisor 1. When SYSTEM datastore is in use the reported free/used/total space is the space on 3PAR CPG. (On the host filesystem there are mostly symlinks and small files that do not require much disk space) -1. Tested/confirmed working on CentOS 7 (Frontend) and Oracle Linux 7, Oracle Linux 8, CentOS 7, CentOS 8, Fedora 29+ (Nodes). +1. Tested/confirmed working on CentOS 7 and Oracle Linux 7 (Frontend), and Oracle Linux 7, Oracle Linux 8, CentOS 7, CentOS 8, Fedora 29+ (Nodes). ## ToDo 1. QOS Priority per VM -1. Configuration of API endpoint and auth in datastore template +1. Configuration of API auth in datastore template ## Installation @@ -172,6 +184,14 @@ DS_MAD_CONF = [ ] ``` +* Edit `/etc/one/oned.conf` and update VM_MAD arguments for 3par + +``` +VM_MAD = [ + ARGUMENTS = "-t 15 -r 0 kvm -l snapshotcreate=snapshot_create-3par,snapshotdelete=snapshot_delete-3par,snapshotrevert=snapshot_revert-3par", + ... +``` + * Enable live disk snapshots support for 3PAR by adding `kvm-3par` to `LIVE_DISK_SNAPSHOTS` variable in `/etc/one/vmm_exec/vmm_execrc` ``` LIVE_DISK_SNAPSHOTS="kvm-qcow2 kvm-ceph kvm-3par" @@ -210,12 +230,18 @@ Some configuration attributes must be set to enable a datastore as 3PAR enabled * **DS_MAD**: [mandatory] The DS driver for the datastore. String, use value `3par` * **TM_MAD**: [mandatory] Transfer driver for the datastore. String, use value `3par` * **DISK_TYPE**: [mandatory for IMAGE datastores] Type for the VM disks using images from this datastore. String, use value `block` +* **API_ENDPOINT**: 3PAR WSAPI Endpoint. String +* **IP**: 3PAR IP address for SSH authentication options for the SSH based calls. String * **CPG**: [mandatory] Name of Common Provisioning Group created on 3PAR. String * **THIN**: Use thin volumes `tpvv` or no. By default enabled. `YES|NO` * **DEDUP**: Use deduplicated thin volumes `tdvv` or no. By default disabled. `YES|NO` * **COMPRESSION**: Use compressed thin volumes or no. By default disabled. `YES|NO` * **NAMING_TYPE**: Part of volume name defining environment. By default `dev`. String (1) -* **BRIDGE_LIST**: Nodes to use for image datastore operations. String (2) +* **BRIDGE_LIST**: [mandatory for IMAGE datastores] Nodes to use for image datastore operations. String (2) +* **REMOTE_COPY**: Enable Remote Copy. `YES|NO` +* **SEC_API_ENDPOINT**: [mandatory when Remote Copy] Secondary 3PAR WSAPI Endpoint. String +* **SEC_IP**: [mandatory when Remote Copy] Secondary 3PAR IP address. String +* **SEC_CPG**: [mandatory when Remote Copy] Name of Common Provisioning Group on Secondary 3PAR. String * **QOS_ENABLE**: Enable QoS. `YES|NO` (3) * **QOS_PRIORITY**: QoS Priority. `HIGH|NORMAL|LOW` (4) * **QOS_MAX\_IOPS**: QoS Max IOPS. Int (5) @@ -326,4 +352,4 @@ $ onedatastore list ## 3PAR best practices guide incl. naming conventions -Please follow the [best practices guide](https://h20195.www2.hpe.com/v2/GetPDF.aspx/4AA4-4524ENW.pdf). +Please follow the [best practices guide](https://support.hpe.com/hpesc/public/docDisplay?docLocale=en_US&docId=a00116000en_us). diff --git a/datastore/3par/3par.py b/datastore/3par/3par.py old mode 100644 new mode 100755 index b4948b5058254756bc530b929f9ccbe495a75bea..e12c52111d7b87643f09a2fd0e5c1b5ed9ec4fca --- a/datastore/3par/3par.py +++ b/datastore/3par/3par.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # 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 # @@ -34,13 +34,16 @@ def boolarg(string): # Common Parser commonParser = argparse.ArgumentParser(add_help=False) commonParser.add_argument('-a', '--api', help='WSAPI Endpoint', required=True) +commonParser.add_argument('-sapi', '--sapi', help='Secondary WSAPI Endpoint', required=False) commonParser.add_argument('-s', '--secure', - help='WSAPI SSL certification verification is disabled. In order to override this,' + help='WSAPI SSL certification verification is disabled. In order to override this, ' 'set this to 1 or to /path/to/cert.crt', type=boolarg, default=False) commonParser.add_argument('-i', '--ip', help='3PAR IP for SSH authentication options for the SSH based calls', required=True) +commonParser.add_argument('-sip', '--sip', help='Secondary 3PAR IP for SSH authentication options for the SSH based calls', + required=False) commonParser.add_argument('-u', '--username', help='3PAR username', required=True) commonParser.add_argument('-p', '--password', help='3PAR password', required=True) commonParser.add_argument('-sd', '--softDelete', help='Soft-delete volumes/snapshots', type=boolarg, default=True) @@ -48,9 +51,17 @@ commonParser.add_argument('-sd', '--softDelete', help='Soft-delete volumes/snaps # MonitorCPG task parser monitorCPGParser = subparsers.add_parser('monitorCPG', parents=[commonParser], help='Get CPG Available Space') monitorCPGParser.add_argument('-c', '--cpg', help='CPG Name', required=True) -monitorCPGParser.add_argument('-d', '--disks', help='Return disks info', type=boolarg, default=False) -monitorCPGParser.add_argument('-di', '--datastoreId', help='DS ID', type=int) -monitorCPGParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', default='dev') + +# MonitorVmDisks task parser +monitorVmDisksParser = subparsers.add_parser('monitorVmDisks', parents=[commonParser], help='Get VM disk stats') +monitorVmDisksParser.add_argument('-di', '--datastoreId', help='DS ID', type=int) +monitorVmDisksParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', default='dev') +monitorVmDisksParser.add_argument('-lf', '--legacyFormat', help='Legacy format to support OpenNebula <5.12', type=boolarg, default=False) + +# EnableRCOnImageDS task parser +enableRCOnImageDSParser = subparsers.add_parser('enableRCOnImageDS', parents=[commonParser], help='Enable RC on image DS. Create RC group and add to it all non-persistent images.') +enableRCOnImageDSParser.add_argument('-di', '--datastoreId', help='DS ID', type=int, required=True) +enableRCOnImageDSParser.add_argument('-rcm', '--remoteCopyMode', help='Remote Copy mode', choices=['SYNC', 'PERIODIC', 'ASYNC'], required=True) # CreateVV task parser createVVParser = subparsers.add_parser('createVV', parents=[commonParser], help='Create new VV') @@ -67,6 +78,7 @@ createVVParser.add_argument('-co', '--comment', help='Comment') deleteVVParser = subparsers.add_parser('deleteVV', parents=[commonParser], help='Delete VV') deleteVVParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', default='dev') deleteVVParser.add_argument('-id', '--id', help='ID of VV to use in VV name', required=True) +deleteVVParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # CloneVV task parser cloneVVParser = subparsers.add_parser('cloneVV', parents=[commonParser], help='Clone specific VV to new one') @@ -89,6 +101,7 @@ copyVVParser = subparsers.add_parser('copyVV', parents=[commonParser], help='Cop copyVVParser.add_argument('-nt', '--namingType', help='Source: Best practices Naming conventions <TYPE> part', default='dev') copyVVParser.add_argument('-id', '--id', help='ID of source VV or VM disk', required=True) +copyVVParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) copyVVParser.add_argument('-d', '--destName', help='Name of the destination VV', required=True) copyVVParser.add_argument('-vi', '--vmId', help='Id of source VV VM') copyVVParser.add_argument('-vc', '--vmClone', help='Is VM clone?', type=boolarg, default=False) @@ -98,6 +111,9 @@ copyVVParser.add_argument('-c', '--cpg', help='Destination VV CPG Name', require growVVParser = subparsers.add_parser('growVV', parents=[commonParser], help='Grow VV by specific size') growVVParser.add_argument('-n', '--name', help='Name of VV to grow', required=True) growVVParser.add_argument('-gb', '--growBy', help='Grow by in MiB', type=int, required=True) +growVVParser.add_argument('-vi', '--vmId', help='Id of VM') +growVVParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', default='dev') +growVVParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # getVVSize task parser getVVSizeParser = subparsers.add_parser('getVVSize', parents=[commonParser], help='Get size of VV') @@ -109,11 +125,13 @@ getVVSizeParser.add_argument('-t', '--type', help='Type of size to get', choices exportVVParser = subparsers.add_parser('exportVV', parents=[commonParser], help='Export VV to host') exportVVParser.add_argument('-n', '--name', help='Name of VV to export', required=True) exportVVParser.add_argument('-hs', '--host', help='Name of host to export to', required=True) +exportVVParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # UnexportVV task parser unexportVVParser = subparsers.add_parser('unexportVV', parents=[commonParser], help='Unexport VV from host') unexportVVParser.add_argument('-n', '--name', help='Name of VV to unexport', required=True) unexportVVParser.add_argument('-hs', '--host', help='Name of host to unexport from', required=True) +unexportVVParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # CreateVmClone task parser createVmCloneParser = subparsers.add_parser('createVmClone', parents=[commonParser], @@ -129,6 +147,7 @@ createVmCloneParser.add_argument('-tpvv', '--tpvv', help='Thin provision', type= createVmCloneParser.add_argument('-tdvv', '--tdvv', help='Thin provision with deduplication', type=boolarg, default=False) createVmCloneParser.add_argument('-compr', '--compression', help='Thin provision compressed volume', type=boolarg, default=False) createVmCloneParser.add_argument('-co', '--comment', help='Comment') +createVmCloneParser.add_argument('-e', '--empty', help='Just create empty volume - not copy from src', type=boolarg, default=False) # CreateVmVV task parser createVmVVParser = subparsers.add_parser('createVmVV', parents=[commonParser], help='Create new VM VV') @@ -162,6 +181,7 @@ createVVSetSnapshotParser.add_argument('-nt', '--namingType', help='Source: Best default='dev') createVVSetSnapshotParser.add_argument('-vi', '--vmId', help='Id of VM') createVVSetSnapshotParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) +createVVSetSnapshotParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # DeleteVVSetSnapshot task parser deleteVVSetSnapshotParser = subparsers.add_parser('deleteVVSetSnapshot', parents=[commonParser], help='Delete volume set snapshot') @@ -169,6 +189,7 @@ deleteVVSetSnapshotParser.add_argument('-nt', '--namingType', help='Source: Best default='dev') deleteVVSetSnapshotParser.add_argument('-vi', '--vmId', help='Id of VM') deleteVVSetSnapshotParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) +deleteVVSetSnapshotParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # CreateSnapshot task parser createSnapshotParser = subparsers.add_parser('createSnapshot', parents=[commonParser], help='Create snapshot of VV') @@ -178,6 +199,7 @@ createSnapshotParser.add_argument('-id', '--id', help='ID of source VV or VM dis createSnapshotParser.add_argument('-vi', '--vmId', help='Id of VM') createSnapshotParser.add_argument('-vc', '--vmClone', help='Is VM clone VV?', type=boolarg, default=False) createSnapshotParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) +createSnapshotParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # RevertSnapshot task parser revertSnapshotParser = subparsers.add_parser('revertSnapshot', parents=[commonParser], @@ -190,6 +212,9 @@ revertSnapshotParser.add_argument('-vc', '--vmClone', help='Is VM clone VV?', ty revertSnapshotParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) revertSnapshotParser.add_argument('-o', '--online', help='Revert snapshot while VV is online (exported)', type=boolarg, default=False) +revertSnapshotParser.add_argument('-off', '--offline', help='Revert snapshot while VV is not attached to VM', type=boolarg, + default=False) +revertSnapshotParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # DeleteSnapshot task parser deleteSnapshotParser = subparsers.add_parser('deleteSnapshot', parents=[commonParser], help='Delete snapshot of VV') @@ -199,12 +224,14 @@ deleteSnapshotParser.add_argument('-id', '--id', help='ID of source VV or VM dis deleteSnapshotParser.add_argument('-vi', '--vmId', help='Id of VM') deleteSnapshotParser.add_argument('-vc', '--vmClone', help='Is VM clone VV?', type=boolarg, default=False) deleteSnapshotParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) +deleteSnapshotParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # FlattenSnapshot task parser flattenSnapshotParser = subparsers.add_parser('flattenSnapshot', parents=[commonParser], help='Promote selected snapshot and delete all snapshots of source VV') flattenSnapshotParser.add_argument('-sn', '--srcName', help='Name of source VV to which snapshot belongs', required=True) flattenSnapshotParser.add_argument('-si', '--snapId', help='ID of snapshot', required=True) +flattenSnapshotParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) # HostExists task parser hostExistsParser = subparsers.add_parser('hostExists', parents=[commonParser], @@ -234,7 +261,6 @@ createQosPolicyParser = subparsers.add_parser('createQosPolicy', parents=[common createQosPolicyParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', default='dev') createQosPolicyParser.add_argument('-vi', '--vmId', help='Id of VM', required=True) -createQosPolicyParser.add_argument('-n', '--name', help='Name of VV', required=True) createQosPolicyParser.add_argument('-qp', '--qosPriority', help='QoS Priority', choices=['LOW', 'NORMAL', 'HIGH'], required=True) createQosPolicyParser.add_argument('-qxi', '--qosMaxIops', help='QoS Max IOPS', type=int, required=True) @@ -242,14 +268,36 @@ createQosPolicyParser.add_argument('-qmi', '--qosMinIops', help='QoS Min IOPS', createQosPolicyParser.add_argument('-qxb', '--qosMaxBw', help='QoS Max BW in kB/s', type=int, required=True) createQosPolicyParser.add_argument('-qmb', '--qosMinBw', help='QoS Min BW in kB/s', type=int, required=True) createQosPolicyParser.add_argument('-ql', '--qosLatency', help='QoS Latency in ms', type=int, required=True) +createQosPolicyParser.add_argument('-rc', '--remoteCopy', help='Enable Remote Copy', type=boolarg, default=False) -# DeleteQosPolicy task parser -deleteQosPolicyParser = subparsers.add_parser('deleteQosPolicy', parents=[commonParser], - help='Delete QoS policy from VM VV set') -deleteQosPolicyParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', +# DisableQosPolicyParser task parser +disableQosPolicyParser = subparsers.add_parser('disableQosPolicy', parents=[commonParser], + help='Disable QoS policy on VM VV set') +disableQosPolicyParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', default='dev') -deleteQosPolicyParser.add_argument('-vi', '--vmId', help='Id of VM', required=True) -deleteQosPolicyParser.add_argument('-n', '--name', help='Name of VV', required=True) +disableQosPolicyParser.add_argument('-vi', '--vmId', help='Id of VM', required=True) + +# addVolumeToRCGroup task parser +addVolumeToRCGroupParser = subparsers.add_parser('addVolumeToRCGroup', parents=[commonParser], + help='Add volume to Remote Copy group. If RC group does not exists, it creates new one') +addVolumeToRCGroupParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', + default='dev') +addVolumeToRCGroupParser.add_argument('-vi', '--vmId', help='Id of VM') +addVolumeToRCGroupParser.add_argument('-n', '--name', help='Name of VV', required=True) +addVolumeToRCGroupParser.add_argument('-rcgn', '--remoteCopyGroupName', help='Name of RC Group', default=None) +addVolumeToRCGroupParser.add_argument('-rcm', '--remoteCopyMode', help='Remote Copy mode', choices=['SYNC', 'PERIODIC', 'ASYNC'], required=True) +addVolumeToRCGroupParser.add_argument('-rcha', '--remoteCopyHA', help='Enable High-Availability mode', type=boolarg, default=True) +addVolumeToRCGroupParser.add_argument('-c', '--cpg', help='Local CPG Name', required=True) +addVolumeToRCGroupParser.add_argument('-sc', '--secCpg', help='Remote CPG Name', required=True) + +# DeleteVolumeFromRCGroup task parser +deleteVolumeFromRCGroupParser = subparsers.add_parser('deleteVolumeFromRCGroup', parents=[commonParser], + help='Delete volume from Remote Copy group. If it is last member, it stop RC group') +deleteVolumeFromRCGroupParser.add_argument('-nt', '--namingType', help='Best practices Naming conventions <TYPE> part', + default='dev') +deleteVolumeFromRCGroupParser.add_argument('-vi', '--vmId', help='Id of VM') +deleteVolumeFromRCGroupParser.add_argument('-n', '--name', help='Name of VV', required=True) +deleteVolumeFromRCGroupParser.add_argument('-rcgn', '--remoteCopyGroupName', help='Name of RC Group', default=None) # ------------ # Define tasks @@ -263,54 +311,113 @@ def monitorCPG(cl, args): free = cpgAvailableSpace.get('usableFreeMiB') total = used + free - print 'USED_MB={used}'.format(used=used) - print 'TOTAL_MB={total}'.format(total=total) - print 'FREE_MB={free}'.format(free=free) - - if args.disks == True: - import subprocess - import xmltodict - - vvs = cl.getVolumes() - diskSizes = {} - - for vv in vvs.get('members'): - diskSizes[vv.get('name')] = vv.get('userSpace').get('usedMiB') - - vmsXml = subprocess.check_output('onevm list --extended -x', shell=True) - vms = xmltodict.parse(vmsXml) - for vm in vms.get('VM_POOL')['VM']: - if args.datastoreId != int(vm.get('HISTORY_RECORDS')['HISTORY'].get('DS_ID')): - continue - + print('USED_MB={used}'.format(used=used)) + print('TOTAL_MB={total}'.format(total=total)) + print('FREE_MB={free}'.format(free=free)) + + +def monitorVmDisks(cl, args): + import subprocess + import xmltodict + from base64 import b64encode + + vvs = cl.getVolumes() + diskSizes = {} + + for vv in vvs.get('members'): + diskSizes[vv.get('name')] = vv.get('userSpace').get('usedMiB') + + vmsXml = subprocess.check_output('onevm list --extended -x', shell=True) + vms = xmltodict.parse(vmsXml, force_list=('VM',)) + if vms['VM_POOL'] is None: + return + for vm in vms.get('VM_POOL')['VM']: + if vm.get('HISTORY_RECORDS') is None or args.datastoreId != int(vm.get('HISTORY_RECORDS')['HISTORY'].get('DS_ID')): + continue + + if args.legacyFormat: result = 'VM=[ID={vmId},POLL="'.format(vmId=vm.get('ID')) - - disks = vm.get('TEMPLATE').get('DISK') - if isinstance(disks,dict): - disks = [disks] - - diskResult = [] - for disk in disks: - if disk.get('CLONE') == 'YES' or disk.get('SOURCE') is None or disk.get('SOURCE') == '': - name = '{namingType}.one.vm.{vmId}.{diskId}.vv'.format(namingType=args.namingType, vmId=vm.get('ID'), diskId=disk.get('DISK_ID')) - else: - source = disk.get('SOURCE').split(':') - name = source[0] + else: + result = 'VM=[ID={vmId},MONITOR="'.format(vmId=vm.get('ID')) + + disks = vm.get('TEMPLATE').get('DISK') + if isinstance(disks,dict): + disks = [disks] + + diskResult = [] + for disk in disks: + if disk.get('CLONE') == 'YES' or disk.get('SOURCE') is None or disk.get('SOURCE') == '': + name = '{namingType}.one.vm.{vmId}.{diskId}.vv'.format(namingType=args.namingType, vmId=vm.get('ID'), diskId=disk.get('DISK_ID')) + else: + source = disk.get('SOURCE').split(':') + name = source[0] + + if name in diskSizes: diskResult.append('DISK_SIZE=[ID={diskId},SIZE={diskSize}]'.format(diskId=disk.get('DISK_ID'), diskSize=diskSizes[name])) - - print result + ' '.join(diskResult) + '"]' + + if args.legacyFormat: + print(result + ' '.join(diskResult) + '"]') + else: + print(result + b64encode(' '.join(diskResult).encode('ascii')).decode('ascii') + '"]') + + +def enableRCOnImageDS(cl, args): + import subprocess + import xmltodict + + # fetch image pool + imagesXml = subprocess.check_output('oneimage list -x'.format(id=args.datastoreId), shell=True) + images = xmltodict.parse(imagesXml, force_list=('IMAGE',)) + + if images['IMAGE_POOL'] is None: + return + + # fetch ds info + dsXml = subprocess.check_output('onedatastore show {id} -x'.format(id=args.datastoreId), shell=True) + ds = xmltodict.parse(dsXml) + dsTemplate = ds.get('DATASTORE').get('TEMPLATE') + + # check if RC is enabled + if dsTemplate.get('REMOTE_COPY') != 'YES': + print('Remote Copy is not enabled for this datastore') + exit(1) + + # prepare RC params + args.remoteCopyGroupName = '{naming_type}.one.ds.{ds_id}'.format(naming_type=dsTemplate.get('NAMING_TYPE'), ds_id=args.datastoreId) + args.remoteCopyHA = False + args.cpg = dsTemplate.get('CPG') + args.secCpg = dsTemplate.get('SEC_CPG') + + # iterate over images + for image in images.get('IMAGE_POOL')['IMAGE']: + # filter out images by datastore ID and we copy only non-persistent + if args.datastoreId != int(image.get('DATASTORE_ID')) or int(image.get('PERSISTENT')) == 1: + continue + + # asign image name and add to remote copy group + source = image.get('SOURCE').split(':') + args.name = source[0] + print('Adding image {name} ({vv}) to RCG {rcgName}'.format(name=image.get('NAME'), vv=args.name, + rcgName=args.remoteCopyGroupName)) + addVolumeToRCGroup(cl, args) + def createVV(cl, args): name = createVVName(args.namingType, args.id) vv = createVVWithName(cl, name, args) wwn = vv.get('wwn').lower() - print '{name}:{wwn}'.format(name=name, wwn=wwn) + print('{name}:{wwn}'.format(name=name, wwn=wwn)) + def deleteVV(cl, args): - name = createVVName(args.namingType, args.id) + vvName = createVVName(args.namingType, args.id) + deleteVVWithName(cl, vvName) - deleteVVWithName(cl, name) + if args.remoteCopy: + vvName = '{name}'.format(name=vvName) + cl = getRemoteSystemClient(args) + deleteVVWithName(cl, vvName) def cloneVV(cl, args): srcName = createVVName(args.srcNamingType, args.srcId) @@ -324,33 +431,57 @@ def cloneVV(cl, args): cl.copyVolume(srcName, destName, args.cpg, optional) wwn = vv.get('wwn').lower() - print '{name}:{wwn}'.format(name=destName, wwn=wwn) + print('{name}:{wwn}'.format(name=destName, wwn=wwn)) + def copyVV(cl, args): + snapId = args.snapId + if args.vmClone == True: srcName = createVmCloneName(args.namingType, args.id, args.vmId) else: srcName = createVVName(args.namingType, args.id) - optional = {'skipZero': True} + if snapId != "-1": + srcName = createSnapshotName(srcName, snapId) + + optional = {'priority': 1, 'skipZero': True} cl.copyVolume(srcName, args.destName, args.cpg, optional) def growVV(cl, args): - cl.growVolume(args.name, args.growBy) + rcgName = '{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + + if args.remoteCopy: + cl.stopRemoteCopy(rcgName) + + try: + cl.growVolume(args.name, args.growBy) + except exceptions.ClientException: + if args.remoteCopy: + cl.startRemoteCopy(rcgName) + + if args.remoteCopy: + cl.startRemoteCopy(rcgName) def getVVSize(cl, args): vv = cl.getVolume(args.name) if args.type == 'USED': - print vv.get('userSpace').get('usedMiB') + print(vv.get('userSpace').get('usedMiB')) elif args.type == 'SNAP': - print vv.get('snapshotSpace').get('usedMiB') + print(vv.get('snapshotSpace').get('usedMiB')) elif args.type == 'VSIZE': - print vv.get('sizeMiB') + print(vv.get('sizeMiB')) + def exportVV(cl, args): - name = args.name + if args.remoteCopy: + name = '{name}'.format(name=args.name) + cl = getRemoteSystemClient(args) + else: + name = args.name + host = args.host # check if VLUN already exists @@ -358,7 +489,7 @@ def exportVV(cl, args): vluns = cl.getHostVLUNs(host) for vlun in vluns: if vlun.get('volumeName') == name: - print vlun.get('lun') + print(vlun.get('lun')) return except exceptions.HTTPNotFound: pass @@ -368,33 +499,54 @@ def exportVV(cl, args): while not done: try: location = cl.createVLUN(name, None, host, None, None, None, True) - print location.split(',')[1] + print (location.split(',')[1]) return except exceptions.HTTPConflict: time.sleep(5) def unexportVV(cl, args): - name = args.name + if args.remoteCopy: + name = '{name}'.format(name=args.name) + cl = getRemoteSystemClient(args) + else: + name = args.name + host = args.host # check if VLUN exists found = False - vluns = cl.getHostVLUNs(host) - for vlun in vluns: - if vlun.get('volumeName') == name: - found = True - break + try: + vluns = cl.getHostVLUNs(host) + for vlun in vluns: + if vlun.get('volumeName') == name: + found = True + break + except exceptions.HTTPNotFound: + pass - if found == False: - return + if found: + cl.deleteVLUN(name, vlun.get('lun'), host) - cl.deleteVLUN(name, vlun.get('lun'), host) def createVmClone(cl, args): destName = createVmCloneName(args.namingType, args.id, args.vmId) - # create new VV - vv = createVVWithName(cl, destName, args) + # check if destination VV exist + try: + vv = cl.getVolume(destName) + if 'expirationTimeSec' in vv: + cl.modifyVolume(destName, {'rmExpTime': True}) + else: + print("Destination volume already exists and it is not mark as deleted!") + exit(1) + except exceptions.HTTPNotFound: + # create new VV + vv = createVVWithName(cl, destName, args) + + if args.empty: + wwn = vv.get('wwn').lower() + print('{name}:{wwn}'.format(name=destName, wwn=wwn)) + exit(0) # define optional for speed up process optional = {'priority': 1, 'skipZero': True} @@ -411,14 +563,15 @@ def createVmClone(cl, args): if i > 5: cl.deleteVolume(destName) cl.logout() - print ex + print(ex) exit(1) i += 1 time.sleep(1) # print info wwn = vv.get('wwn').lower() - print '{name}:{wwn}'.format(name=destName, wwn=wwn) + print('{name}:{wwn}'.format(name=destName, wwn=wwn)) + def createVmVV(cl, args): name = createVmCloneName(args.namingType, args.id, args.vmId) @@ -428,13 +581,15 @@ def createVmVV(cl, args): # print info wwn = vv.get('wwn').lower() - print '{name}:{wwn}'.format(name=name, wwn=wwn) + print('{name}:{wwn}'.format(name=name, wwn=wwn)) + def getVmClone(cl, args): name = createVmCloneName(args.namingType, args.id, args.vmId) vv = cl.getVolume(name) wwn = vv.get('wwn').lower() - print '{name}:{wwn}'.format(name=name, wwn=wwn) + print('{name}:{wwn}'.format(name=name, wwn=wwn)) + def deleteVmClone(cl, args): name = createVmCloneName(args.namingType, args.id, args.vmId) @@ -444,25 +599,32 @@ def deleteVmClone(cl, args): def createVVSetSnapshot(cl, args): snapId = 's{snapId}'.format(snapId=args.snapId) - vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + + if args.remoteCopy: + vvsetName = 'RCP_{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + else: + vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + # limit length of vvset name to 27 chars + # it is limit of 3par system + vvsetName = vvsetName[0:27] # get volume set info try: vvset = cl.getVolumeSet(vvsetName) members = vvset.get('setmembers') except exceptions.HTTPNotFound: - print 'Volume set does not exits, exiting...' + print('Volume set does not exits, exiting...') return # no members in volume set? unexpected if not members or not len(members) > 0: - print 'Volume set has no members, exiting...' + print('Volume set has no members, exiting...') return # check for soft deleted snapshot if args.softDelete: for member in members: - snapName, metaKey = createSnapshotNameAndMetaKey(member, snapId) + snapName = createSnapshotName(member, snapId) try: cl.getVolume(snapName) # snap exists, so delete it @@ -470,62 +632,76 @@ def createVVSetSnapshot(cl, args): except exceptions.HTTPNotFound: pass + if args.remoteCopy: + scl = getRemoteSystemClient(args) + try: + scl.getVolume(snapName) + # snap exists, so delete it + scl.deleteVolume(snapName) + except exceptions.HTTPNotFound: + pass + # prepare snapshot vv name pattern name = '@vvname@.{snapId}'.format(snapId=snapId) # create snapshots - cl.createSnapshotOfVolumeSet(name, vvsetName, {'readOnly': True}) - - # create and add snapshot metadata to all members - for member in members: - snapName, metaKey = createSnapshotNameAndMetaKey(member, snapId) - cl.setVolumeMetaData(member, metaKey, snapName) + if args.remoteCopy: + rcgName = 'rcgroup:{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + cmd = ['createsv', '-rcopy', '-ro', name, rcgName] + cl._run(cmd) + else: + cl.createSnapshotOfVolumeSet(name, vvsetName, {'readOnly': True}) - print args.snapId + print(args.snapId) def deleteVVSetSnapshot(cl, args): snapId = 's{snapId}'.format(snapId=args.snapId) - vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + scl = None + + if args.remoteCopy: + scl = getRemoteSystemClient(args) + vvsetName = 'RCP_{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + else: + vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + # limit length of vvset name to 27 chars + # it is limit of 3par system + vvsetName = vvsetName[0:27] # get volume set info try: vvset = cl.getVolumeSet(vvsetName) members = vvset.get('setmembers') except exceptions.HTTPNotFound: - print 'Volume set does not exits, exiting...' + print('Volume set does not exits, exiting...') return # no members in volume set? unexpected if not members or not len(members) > 0: - print 'Volume set has no members, exiting...' + print('Volume set has no members, exiting...') return # iterate over volumes and find snapshots to delete for member in members: - name, metaKey = createSnapshotNameAndMetaKey(member, snapId) + name = createSnapshotName(member, snapId) if args.softDelete: cl.modifyVolume(name, {'expirationHours': 168}) + args.remoteCopy and scl.modifyVolume(name, {'expirationHours': 168}) else: cl.deleteVolume(name) - - try: - cl.removeVolumeMetaData(member, metaKey) - except exceptions.HTTPNotFound: - pass - + args.remoteCopy and scl.deleteVolume(name) def createSnapshot(cl, args): snapId = args.snapId - if args.vmClone == True: + if args.vmClone: srcName = createVmCloneName(args.namingType, args.id, args.vmId) else: srcName = createVVName(args.namingType, args.id) - name, metaKey = createSnapshotNameAndMetaKey(srcName, snapId) + name = createSnapshotName(srcName, snapId) # check for soft deleted snapshot if args.softDelete: @@ -536,119 +712,194 @@ def createSnapshot(cl, args): except exceptions.HTTPNotFound: pass - cl.createSnapshot(name, srcName, {'readOnly': True}) - cl.setVolumeMetaData(srcName, metaKey, name) + if args.remoteCopy: + scl = getRemoteSystemClient(args) + try: + scl.getVolume(name) + # snap exists, so delete it + scl.deleteVolume(name) + except exceptions.HTTPNotFound: + pass + + optional = {'readOnly': True} + + if args.remoteCopy: + optional['syncSnapRcopy'] = True + + cl.createSnapshot(name, srcName, optional) def revertSnapshot(cl, args): snapId = args.snapId + rcgName = '{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) - if args.vmClone == True: + if args.vmClone: srcName = createVmCloneName(args.namingType, args.id, args.vmId) else: srcName = createVVName(args.namingType, args.id) - name, metaKey = createSnapshotNameAndMetaKey(srcName, snapId) + name = createSnapshotName(srcName, snapId) optional = {'online': args.online} - cl.promoteVirtualCopy(name, optional) + if not args.offline and args.remoteCopy: + cl.stopRemoteCopy(rcgName) + optional['allowRemoteCopyParent'] = True + + try: + cl.promoteVirtualCopy(name, optional) + except exceptions.ClientException as ex: + if not args.offline and args.remoteCopy: + cl.startRemoteCopy(rcgName) + + parser.error(ex) + + if not args.offline and args.remoteCopy: + # need to wait for snapshot promoting + done = False + i = 0 + while not done: + try: + cl.startRemoteCopy(rcgName) + done = True + except exceptions.HTTPBadRequest as ex: + # wait max 5min + if i > 60: + # other issue, exiting + cl.logout() + print(ex) + exit(1) + i += 1 + + time.sleep(5) def deleteSnapshot(cl, args): snapId = args.snapId + scl = None - if args.vmClone == True: + if args.remoteCopy: + scl = getRemoteSystemClient(args) + + if args.vmClone: srcName = createVmCloneName(args.namingType, args.id, args.vmId) else: srcName = createVVName(args.namingType, args.id) - name, metaKey = createSnapshotNameAndMetaKey(srcName, snapId) - - if args.softDelete: - cl.modifyVolume(name, {'expirationHours': 168}) - else: - cl.deleteVolume(name) + name = createSnapshotName(srcName, snapId) + # local try: - cl.removeVolumeMetaData(srcName, metaKey) + if args.softDelete: + cl.modifyVolume(name, {'expirationHours': 168}) + else: + cl.deleteVolume(name) except exceptions.HTTPNotFound: pass + # remote + if args.remoteCopy: + try: + if args.softDelete: + scl.modifyVolume(name, {'expirationHours': 168}) + else: + scl.deleteVolume(name) + except exceptions.HTTPNotFound: + pass + def flattenSnapshot(cl, args): srcName = args.srcName snapId = args.snapId + scl = None - name, metaKey = createSnapshotNameAndMetaKey(srcName, snapId) + if args.remoteCopy: + scl = getRemoteSystemClient(args) + + name = createSnapshotName(srcName, snapId) # promote selected snapshot cl.promoteVirtualCopy(name) # delete all snapshots - meta = cl.getAllVolumeMetaData(srcName) - for data in meta.get('members'): - key = data.get('key') - if key.startswith('snap'): - snap = data.get('value') + snapshots = cl.getVolumeSnapshots(srcName) + for snap in snapshots: + # local + try: + if args.softDelete: + cl.modifyVolume(snap, {'expirationHours': 168}) + else: + # need to wait for snapshot promoting + done = False + while not done: + try: + cl.deleteVolume(snap) + done = True + except exceptions.HTTPConflict: + time.sleep(5) + except exceptions.HTTPNotFound: + pass + + # remote + if args.remoteCopy: try: if args.softDelete: - cl.modifyVolume(snap, {'expirationHours': 168}) + scl.modifyVolume(snap, {'expirationHours': 168}) else: - # need to wait for snapshot promoting - done = False - while not done: - try: - cl.deleteVolume(snap) - done = True - except exceptions.HTTPConflict: - time.sleep(5) - # snapshot deleted, remove metadata - cl.removeVolumeMetaData(srcName, key) + scl.deleteVolume(snap) except exceptions.HTTPNotFound: - # snapshot already not exists, remove metadata - cl.removeVolumeMetaData(srcName, key) + pass + def hostExists(cl, args): try: cl.getHost(args.host) except exceptions.HTTPNotFound: - print 0 + print(0) return - print 1 + print(1) + def addVolumeToVVSet(cl, args): vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + # limit length of vvset name to 27 chars + # it is limit of 3par system + vvsetName = vvsetName[0:27] + # get or create vvset try: cl.getVolumeSet(vvsetName) except exceptions.HTTPNotFound: - print 'Volume Set does not exists, create new' + print('Volume Set does not exists, create new') cl.createVolumeSet(vvsetName, None, args.comment) # add volume to vvset try: cl.addVolumeToVolumeSet(vvsetName, args.name) except exceptions.HTTPConflict as ex: - print 'VV already mapped to VV Set' + print('VV already mapped to VV Set') def deleteVolumeFromVVSet(cl, args): vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + # limit length of vvset name to 27 chars + # it is limit of 3par system + vvsetName = vvsetName[0:27] + # remove volume from volume set try: cl.removeVolumeFromVolumeSet(vvsetName, args.name) except exceptions.HTTPNotFound: - print 'Volume is already removed from vv set' + print('Volume is already removed from vv set') # get volume set info try: vvset = cl.getVolumeSet(vvsetName) members = vvset.get('setmembers') except exceptions.HTTPNotFound: - print 'Volume set does not exits, exiting...' + print('Volume set does not exits, exiting...') return # if there are other members them we do not remove VV Set @@ -659,66 +910,240 @@ def deleteVolumeFromVVSet(cl, args): try: cl.deleteVolumeSet(vvsetName) except exceptions.HTTPNotFound: - print 'VV Set already does not exits' + print('VV Set already does not exits') def createQosPolicy(cl, args): - vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + if args.remoteCopy: + vvsetName = 'RCP_{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + else: + vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + # limit length of vvset name to 27 chars + # it is limit of 3par system + vvsetName = vvsetName[0:27] # create QoS policy if not exists qosRules = prepareQosRules(args) + + # primary system + setQosRules(cl, vvsetName, qosRules) + + # remote system + if args.remoteCopy: + scl = getRemoteSystemClient(args) + sysId = int(cl.getStorageSystemInfo().get('id')) + secVvsetName = '{name}.r{sysId}'.format(name=vvsetName, sysId=sysId) + # strip to 27 chars, like 3par + setQosRules(scl, secVvsetName[0:27], qosRules) + + +def disableQosPolicy(cl, args): + vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) + # limit length of vvset name to 27 chars + # it is limit of 3par system + vvsetName = vvsetName[0:27] + try: - qos = cl.queryQoSRule(vvsetName) - # compare rules - for k, v in qosRules.items(): - if k == 'enable': - k = 'enabled' - if qos.get(k) != v: - # not match, update - print 'QoS Policy Rules changed, need update' - cl.modifyQoSRules(vvsetName, qosRules) - break + cl.modifyQoSRules(vvsetName, {'enable': False}) except exceptions.HTTPNotFound: - print 'QoS Policy does not exists, create new' - cl.createQoSRules(vvsetName, qosRules) + pass -def deleteQosPolicy(cl, args): - vvsetName = '{namingType}.one.vm.{vmId}.vvset'.format(namingType=args.namingType, vmId=args.vmId) +def addVolumeToRCGroup(cl, args): + shouldStartRcg = True - # get volume set info + if args.remoteCopyGroupName is None: + rcgName = '{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + else: + rcgName = args.remoteCopyGroupName + + scl, targetName = getRemoteCopyTargetName(args) + + # get or create rc group try: - vvset = cl.getVolumeSet(vvsetName) - members = vvset.get('setmembers') + rcg = cl.getRemoteCopyGroup(rcgName) + rcgState = rcg.get('targets')[0].get('state') + # rc group already in starting/started state + if rcgState == 2 or rcgState == 3: + shouldStartRcg = False + # check for peer persistence enabled + if rcg.get("targets")[0].get("policies").get("pathManagement"): + cl.stopRemoteCopy(rcgName) + shouldStartRcg = True except exceptions.HTTPNotFound: - print 'Volume set does not exits, exiting...' - return + print('Remote Copy group does not exists, create new') + targets, optional, policies = getRCGroupParams(targetName, args) + cl.createRemoteCopyGroup(rcgName, targets, optional) + # modify to add specific options + cl.modifyRemoteCopyGroup(rcgName, {'targets': [{'policies': policies}]}) + # modify to add autoSync policy, not exposed via API + cl._run(['setrcopygroup', 'pol', 'auto_synchronize', rcgName]) + + # add volume to rc group + # we need to have same VV name on second system too + secVVName = '{name}'.format(name=args.name) + volumeAutoCreation = True + skipInitialSync = False + + # check if remote VV exist + try: + secVV = scl.getVolume(secVVName) + if 'expirationTimeSec' in secVV: + scl.modifyVolume(secVVName, {'rmExpTime': True}) + volumeAutoCreation = False + skipInitialSync = True + except exceptions.HTTPNotFound: + pass - # if there are other members them we do not remove QoS Policy - if members and len(members) > 0: - return + target = { + 'targetName': targetName, + 'secVolumeName': secVVName + } + + done = False + i = 0 + while not done: + try: + print('Add volume to Remote Copy group') + cl.addVolumeToRemoteCopyGroup(rcgName, args.name, [target], { + 'volumeAutoCreation': volumeAutoCreation, + 'skipInitialSync': skipInitialSync + }) + + done = True - # delete qos policy + # start rc group + if shouldStartRcg: + print('Start Remote Copy group') + cl.startRemoteCopy(rcgName) + except exceptions.HTTPForbidden as ex: + # there can be physical copy in progress, so we need wait and retry + # wait max 15min + if i > 180: + # other issue, exiting + cl.logout() + scl.logout() + print(ex) + exit(1) + i += 1 + time.sleep(5) + except exceptions.HTTPConflict as ex: + # volume is already in RC Group + print(ex) + done = True + + scl.logout() + + +def deleteVolumeFromRCGroup(cl, args): + shouldStartRcg = False + + if args.remoteCopyGroupName is None: + rcgName = '{namingType}.one.vm.{vmId}'.format(namingType=args.namingType, vmId=args.vmId) + else: + rcgName = args.remoteCopyGroupName + + # remove volume from rc group try: - cl.deleteQoSRules(vvsetName) + rcg = cl.getRemoteCopyGroup(rcgName) + rcgState = rcg.get('targets')[0].get('state') + # check if rc group in starting/started state + if rcgState == 2 or rcgState == 3: + # we need stop rcg only if peer persistence enabled + if rcg.get("targets")[0].get("policies").get("pathManagement"): + cl.stopRemoteCopy(rcgName) + shouldStartRcg = True + + cl.removeVolumeFromRemoteCopyGroup(rcgName, args.name) except exceptions.HTTPNotFound: - print 'QoS Policy already does not exits' + print('Volume is already removed from rc group') + + # get rc group info + try: + rcg = cl.getRemoteCopyGroup(rcgName) + volumes = rcg.get('volumes') + except exceptions.HTTPNotFound: + print('Remote Copy group does not exits, exiting...') + return + + # if there are other members them we do not remove VV Set + if volumes and len(volumes) > 0: + if shouldStartRcg: + print('Start Remote Copy group') + # start rc group back + cl.startRemoteCopy(rcgName) + else: + # delete rc group + try: + cl.removeRemoteCopyGroup(rcgName) + except exceptions.HTTPNotFound: + print('Remote Copy group does not exits') # ---------------- # Helper functions # ---------------- -def createVVName(namingType, id): - return '{namingType}.one.{id}.vv'.format(namingType=namingType, id=id) +def getRCGroupParams(targetName, args): + target = {'targetName': targetName} + policies = {'autoRecover': True} + + if args.remoteCopyMode == 'SYNC': + target['mode'] = 1 + if args.remoteCopyHA: + #policies['autoFailover'] = True + policies['pathManagement'] = True + elif args.remoteCopyMode == 'PERIODIC': + target['mode'] = 3 + elif args.remoteCopyMode == 'ASYNC': + target['mode'] = 4 + + target['userCPG'] = args.secCpg + target['snapCPG'] = args.secCpg + + optional = { + 'localSnapCPG': args.cpg, + 'localUserCPG': args.cpg + } + + return [target], optional, policies + +def getRemoteCopyTargetName(args): + scl = getRemoteSystemClient(args) + + targetName = scl.getStorageSystemInfo().get('name').encode('ascii', 'ignore') + + return scl, targetName + +def getRemoteSystemClient(args): + # check for remoteCopy required args + if args.sapi is None or args.sip is None: + parser.error("--remoteCopy requires --sapi and --sip.") + + secure = False + if args.secure: + secure = True -def createVmCloneName(namingType, id, vmId): - return '{namingType}.one.vm.{vmId}.{id}.vv'.format(namingType=namingType, id=id, vmId=vmId) + scl = client.HPE3ParClient(args.sapi, False, secure, None, True) + scl.setSSHOptions(args.sip, args.username, args.password) -def createSnapshotNameAndMetaKey(srcName, snapId): + try: + scl.login(args.username, args.password) + except exceptions.HTTPUnauthorized as ex: + print("Remote system: Login failed.") + + return scl + + +def createVVName(namingType, imageId): + return '{namingType}.one.{id}.vv'.format(namingType=namingType, id=imageId) + +def createVmCloneName(namingType, diskId, vmId): + return '{namingType}.one.vm.{vmId}.{id}.vv'.format(namingType=namingType, id=diskId, vmId=vmId) + +def createSnapshotName(srcName, snapId): name = '{srcName}.{snapId}'.format(srcName=srcName, snapId=snapId) - metaKey = 'snap{snapId}'.format(snapId=snapId) - return name, metaKey + return name def createVVWithName(cl, name, args): cpgName = args.cpg @@ -727,13 +1152,13 @@ def createVVWithName(cl, name, args): if args.tpvv == True and args.tdvv != True and args.compression != True: optional['tpvv'] = True - if args.tdvv == True: + if args.tdvv: optional['tdvv'] = True if args.compression == True and args.size >= 16384: optional['compression'] = True - # minumum size for volume is 256MiB + # minimum size for volume is 256MiB if args.size < 256: args.size = 256 @@ -746,25 +1171,22 @@ def createVVWithName(cl, name, args): def deleteVVWithName(cl, name): if args.softDelete: - cl.modifyVolume(name, {'expirationHours': 168}) - # find and delete snapshots - meta = cl.getAllVolumeMetaData(name) - for data in meta.get('members'): - key = data.get('key') - if key.startswith('snap'): - snap = data.get('value') + try: + cl.modifyVolume(name, {'expirationHours': 168}) + # find and delete snapshots + snapshots = cl.getVolumeSnapshots(name) + for snap in snapshots: cl.modifyVolume(snap, {'expirationHours': 168}) + except exceptions.HTTPNotFound: + pass else: try: cl.deleteVolume(name) except exceptions.HTTPConflict: # try to find and delete snapshots - meta = cl.getAllVolumeMetaData(name) - for data in meta.get('members'): - key = data.get('key') - if key.startswith('snap'): - snap = data.get('value') - cl.deleteVolume(snap) + snapshots = cl.getVolumeSnapshots(name) + for snap in snapshots: + cl.deleteVolume(snap) # try delete again done = False @@ -779,10 +1201,12 @@ def deleteVVWithName(cl, name): if i > 180: # other issue, exiting cl.logout() - print ex + print(ex) exit(1) i += 1 time.sleep(5) + except exceptions.HTTPNotFound: + pass def prepareQosRules(args): qosRules = { @@ -801,7 +1225,7 @@ def prepareQosRules(args): qosRules['bwMinGoalKB'] = 1 if args.qosLatency == 0: - qosRules['latencyGoal'] = 5000 + qosRules['latencyGoal'] = None qosRules['defaultLatency'] = True if args.qosPriority == 'LOW': @@ -813,6 +1237,24 @@ def prepareQosRules(args): return qosRules +def setQosRules(cl, vvsetName, qosRules): + try: + qos = cl.queryQoSRule(vvsetName) + # compare rules + for k, v in qosRules.items(): + if k == 'enable': + k = 'enabled' + if k == 'defaultLatency' and v: + v = None + if qos.get(k) != v: + # not match, update + print('QoS Policy Rules changed, need update') + cl.modifyQoSRules(vvsetName, qosRules) + break + except exceptions.HTTPNotFound: + print('QoS Policy does not exists, create new') + cl.createQoSRules(vvsetName, qosRules) + # ------------------------------------- # Parse args and proceed with execution # ------------------------------------- @@ -822,7 +1264,7 @@ args = parser.parse_args() # Login and run task # ------------------ secure = False -if args.secure == True: +if args.secure: secure = True cl = client.HPE3ParClient(args.api, False, secure, None, True) @@ -831,13 +1273,13 @@ cl.setSSHOptions(args.ip, args.username, args.password) try: cl.login(args.username, args.password) except exceptions.HTTPUnauthorized as ex: - print "Login failed." + print("Login failed.") try: globals()[args.task](cl, args) cl.logout() except Exception as ex: # something unexpected happened - print ex + print(ex) cl.logout() exit(1) diff --git a/datastore/3par/clone b/datastore/3par/clone index a9fdcf798447374a9693492d97aa2d0c50bd71e9..248d90d8d3ab99ec18a5db33aeda8397adec046a 100644 --- a/datastore/3par/clone +++ b/datastore/3par/clone @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -33,7 +33,13 @@ fi . $LIB_LOCATION/sh/scripts_common.sh DRIVER_PATH=$(dirname $0) +source ${DRIVER_PATH}/../libfs.sh source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf +. ${DRIVER_PATH}/scripts_3par.sh + +# preserve vars from conf file +CONF_API_ENDPOINT="$API_ENDPOINT" +CONF_IP="$IP" # -------- Get cp and datastore arguments from OpenNebula core ------------ @@ -42,53 +48,188 @@ ID=$2 XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/ID \ +done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/RESTRICTED_DIRS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SAFE_DIRS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/STAGING_DIR \ + /DS_DRIVER_ACTION_DATA/DATASTORE/ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/REMOTE_COPY \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_CPG \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/THIN \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/DEDUP \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/COMPRESSION \ - /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE \ /DS_DRIVER_ACTION_DATA/IMAGE/NAME \ + /DS_DRIVER_ACTION_DATA/IMAGE/PERSISTENT \ /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ /DS_DRIVER_ACTION_DATA/IMAGE/CLONING_ID) -unset i - -DSID="${XPATH_ELEMENTS[i++]}" -CPG="${XPATH_ELEMENTS[i++]:-$CPG}" -THIN="${XPATH_ELEMENTS[i++]:-$THIN}" -DEDUP="${XPATH_ELEMENTS[i++]:-$DEDUP}" -COMPRESSION="${XPATH_ELEMENTS[i++]:-$COMPRESSION}" -NAMING_TYPE="${XPATH_ELEMENTS[i++]:-$NAMING_TYPE}" -NAME="${XPATH_ELEMENTS[i++]}" -SIZE="${XPATH_ELEMENTS[i++]}" -CLONING_ID="${XPATH_ELEMENTS[i++]}" - -# Get source image properties -unset i XPATH_ELEMENTS -XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" +BASE_PATH="${XPATH_ELEMENTS[j++]}" +RESTRICTED_DIRS="${XPATH_ELEMENTS[j++]}" +SAFE_DIRS="${XPATH_ELEMENTS[j++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[j++]}" +STAGING_DIR="${XPATH_ELEMENTS[j++]:-/var/tmp}" +DSID="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" +THIN="${XPATH_ELEMENTS[j++]:-$THIN}" +DEDUP="${XPATH_ELEMENTS[j++]:-$DEDUP}" +COMPRESSION="${XPATH_ELEMENTS[j++]:-$COMPRESSION}" +NAME="${XPATH_ELEMENTS[j++]//[^A-Za-z0-9\[\]() _~+-]/}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" +SIZE="${XPATH_ELEMENTS[j++]}" +CLONING_ID="${XPATH_ELEMENTS[j++]}" + +#------------------------------------------------------------------------------- +# Get source image datastore id and source +#------------------------------------------------------------------------------- +XPATH="${DRIVER_PATH}/../xpath.rb --stdin" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(oneimage show -x $CLONING_ID | $XPATH \ + /IMAGE/DATASTORE_ID \ + /IMAGE/SOURCE) + +SRC_DS_ID="${XPATH_ELEMENTS[j++]}" +SRC_NAME_WWN="${XPATH_ELEMENTS[j++]}" + +#------------------------------------------------------------------------------- +# Get source image ds information +#------------------------------------------------------------------------------- +unset i j XPATH_ELEMENTS + while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(oneimage show -x $CLONING_ID| $XPATH \ - /IMAGE/DATASTORE_ID) +done < <(onedatastore show -x $SRC_DS_ID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP) + +SRC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$CONF_API_ENDPOINT}" +SRC_IP="${XPATH_ELEMENTS[j++]:-$CONF_IP}" + +# clone between storage systems +if [ "$SRC_IP" != "$IP" ]; then + # get dst host from bridge list + DST_HOST=`get_destination_host $ID` + + if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit 1 + fi + + set_up_datastore "$BASE_PATH" "$RESTRICTED_DIRS" "$SAFE_DIRS" + + # prepare tmp dest + IMAGE_HASH=`generate_image_hash` + TMP_DST="$STAGING_DIR/$IMAGE_HASH" + + # extract VV Name and Wwn + SRC_NAME=$(get_vv_name "$SRC_NAME_WWN") + SRC_WWN=$(get_vv_wwn "$SRC_NAME_WWN") + + log "Mapping $SRC_NAME to $DST_HOST" + LUN=$(python ${DRIVER_PATH}/3par.py exportVV -a "$SRC_API_ENDPOINT" -i "$SRC_IP" -s "$SECURE" -u "$USERNAME" \ + -p "$PASSWORD" -n "$SRC_NAME" -hs "$DST_HOST") + + if [ $? -ne 0 ]; then + error_message "$LUN" + exit 1 + fi -SRC_DSID=${XPATH_ELEMENTS[0]} + map_lun "$DST_HOST" "$LUN" "$SRC_WWN" "$STAGING_DIR" "$TMP_DST" + + # Create image + NAME_WWN=$(python ${DRIVER_PATH}/3par.py createVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -nt $NAMING_TYPE -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION -id $ID \ + -c $CPG -sz $SIZE -co "$NAME") + + NEW_NAME=$(get_vv_name "$NAME_WWN") + NEW_WWN=$(get_vv_wwn "$NAME_WWN") + + # export new vv and clone old one to it + LUN=$(export_vv "$NEW_NAME" "$DST_HOST") + map_and_copy_to_lun "$DST_HOST" "$SRC_WWN" "$LUN" "$NEW_WWN" + + # unexport new vv + unmap_lun "$DST_HOST" "$NEW_WWN" + unexport_vv "$NEW_NAME" "$DST_HOST" + + # unexport src vv + unmap_lun "$DST_HOST" "$SRC_WWN" + + log "Unexporting $SRC_NAME from $DST_HOST" + RESULT=$(python ${DRIVER_PATH}/3par.py unexportVV -a "$SRC_API_ENDPOINT" -i "$SRC_IP" -s "$SECURE" -u "$USERNAME" \ + -p "$PASSWORD" -n "$SRC_NAME" -hs "$DST_HOST") + + if [ $? -ne 0 ]; then + error_message "$RESULT" + exit 1 + fi + + # cleanup + CLEANUP_CMD=$(cat <<EOF + # remove original + $RM -f $TMP_DST +EOF +) + + ssh_exec_and_log "$DST_HOST" "$CLEANUP_CMD" \ + "Error performing cleanup on $DST_HOST" +else + # -------- Clone image ------------ + NAME_WWN=$(python ${DRIVER_PATH}/3par.py cloneVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -snt $NAMING_TYPE -sid $CLONING_ID -nt $NAMING_TYPE -id $ID \ + -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION -c $CPG -sz $SIZE -co "$NAME") + + if [ $? -ne 0 ]; then + error_message "$NAME_WWN" + exit 1 + fi +fi + +#------------------------------------------------------------------------------- +# Clone operation take a while and in meantime, persistent attr can be changed +# so check again, for image persistent state +#------------------------------------------------------------------------------- +unset i j XPATH_ELEMENTS -# Get source datastore properties -unset i XPATH_ELEMENTS -XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SRC_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) +done < <(oneimage show -x $ID| $XPATH /IMAGE/PERSISTENT) -SRC_NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" -# -------- Clone image ------------ +# ----------- Add image to RC Group ------- +# only non-persistent images +if [ "$PERSISTENT" == "0" ] && [ "$REMOTE_COPY" == "YES" ]; then + VV_NAME=$(get_vv_name "$NAME_WWN") + + log "Add image to remote copy group" + RCG=$(python ${DRIVER_PATH}/3par.py addVolumeToRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $VV_NAME \ + -c $CPG -sc $SEC_CPG -rcm $REMOTE_COPY_MODE -rcgn "$NAMING_TYPE.one.ds.$DSID" -rcha "NO") + + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi +fi -python ${DRIVER_PATH}/3par.py cloneVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -snt $SRC_NAMING_TYPE -sid $CLONING_ID -nt $NAMING_TYPE -id $ID \ - -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION -c $CPG -sz $SIZE -co "$NAME" \ No newline at end of file +echo $NAME_WWN diff --git a/datastore/3par/cp b/datastore/3par/cp index 76679a1784aca034d058b36f14dc3708be416ef9..cb792972d1fe14e90a299d08c95ac9ceefa77497 100644 --- a/datastore/3par/cp +++ b/datastore/3par/cp @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -49,7 +49,7 @@ UTILS_PATH="${DRIVER_PATH}/.." XPATH="$UTILS_PATH/xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" @@ -67,37 +67,49 @@ done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CONVERT \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/DRIVER \ /DS_DRIVER_ACTION_DATA/IMAGE/TYPE \ + /DS_DRIVER_ACTION_DATA/DATASTORE/ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/REMOTE_COPY \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_CPG \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/THIN \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/DEDUP \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/COMPRESSION \ - /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE \ /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ + /DS_DRIVER_ACTION_DATA/IMAGE/PERSISTENT \ /DS_DRIVER_ACTION_DATA/IMAGE/NAME) -unset i - -BASE_PATH="${XPATH_ELEMENTS[i++]}" -RESTRICTED_DIRS="${XPATH_ELEMENTS[i++]}" -SAFE_DIRS="${XPATH_ELEMENTS[i++]}" -BRIDGE_LIST="${XPATH_ELEMENTS[i++]}" -STAGING_DIR="${XPATH_ELEMENTS[i++]:-/var/tmp}" -TYPE="${XPATH_ELEMENTS[i++]}" -SRC="${XPATH_ELEMENTS[i++]}" -MD5="${XPATH_ELEMENTS[i++]}" -SHA1="${XPATH_ELEMENTS[i++]}" -NO_DECOMPRESS="${XPATH_ELEMENTS[i++]}" -LIMIT_TRANSFER_BW="${XPATH_ELEMENTS[i++]}" -CONVERT="${XPATH_ELEMENTS[i++]:-yes}" -DRIVER="${XPATH_ELEMENTS[i++]}" -IMAGE_TYPE="${XPATH_ELEMENTS[i++]}" -CPG="${XPATH_ELEMENTS[i++]:-$CPG}" -THIN="${XPATH_ELEMENTS[i++]:-$THIN}" -DEDUP="${XPATH_ELEMENTS[i++]:-$DEDUP}" -COMPRESSION="${XPATH_ELEMENTS[i++]:-$COMPRESSION}" -NAMING_TYPE="${XPATH_ELEMENTS[i++]:-$NAMING_TYPE}" -SIZE="${XPATH_ELEMENTS[i++]:-0}" -IMAGE_NAME="${XPATH_ELEMENTS[i++]:-0}" +BASE_PATH="${XPATH_ELEMENTS[j++]}" +RESTRICTED_DIRS="${XPATH_ELEMENTS[j++]}" +SAFE_DIRS="${XPATH_ELEMENTS[j++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[j++]}" +STAGING_DIR="${XPATH_ELEMENTS[j++]:-/var/tmp}" +TYPE="${XPATH_ELEMENTS[j++]}" +SRC="${XPATH_ELEMENTS[j++]}" +MD5="${XPATH_ELEMENTS[j++]}" +SHA1="${XPATH_ELEMENTS[j++]}" +NO_DECOMPRESS="${XPATH_ELEMENTS[j++]}" +LIMIT_TRANSFER_BW="${XPATH_ELEMENTS[j++]}" +CONVERT="${XPATH_ELEMENTS[j++]:-yes}" +DRIVER="${XPATH_ELEMENTS[j++]}" +IMAGE_TYPE="${XPATH_ELEMENTS[j++]}" +DSID="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" +THIN="${XPATH_ELEMENTS[j++]:-$THIN}" +DEDUP="${XPATH_ELEMENTS[j++]:-$DEDUP}" +COMPRESSION="${XPATH_ELEMENTS[j++]:-$COMPRESSION}" +SIZE="${XPATH_ELEMENTS[j++]:-0}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" +IMAGE_NAME="${XPATH_ELEMENTS[j++]//[^A-Za-z0-9\[\]() _~+-]/}" DST_HOST=`get_destination_host $ID` @@ -150,6 +162,20 @@ fi NAME=$(get_vv_name "$NAME_WWN") WWN=$(get_vv_wwn "$NAME_WWN") +# ----------- Add image to RC Group ------- +# only non-persistent images +if [ "$PERSISTENT" == "0" ] && [ "$REMOTE_COPY" == "YES" ]; then + log "Add image to remote copy group" + RCG=$(python ${DRIVER_PATH}/3par.py addVolumeToRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME \ + -c $CPG -sc $SEC_CPG -rcm $REMOTE_COPY_MODE -rcgn "$NAMING_TYPE.one.ds.$DSID" -rcha "NO") + + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi +fi + # Map image log "Mapping $WWN to $DST_HOST" @@ -163,22 +189,13 @@ fi DISCOVER_CMD=$(cat <<EOF set -e $(discover_lun "$LUN" "$WWN") - echo "\$DEV" EOF ) ssh_exec_and_log "$DST_HOST" "$DISCOVER_CMD" \ "Error registering $WWN to $DST_HOST" -GET_DEV_CMD=$(cat <<EOF - set -e - DEV="/dev/mapper/3$WWN" - - echo "\$DEV" -EOF -) - -DEV=$(ssh_monitor_and_log "$DST_HOST" "$GET_DEV_CMD") +DEV="/dev/mapper/3$WWN" # copy image REGISTER_CMD=$(cat <<EOF @@ -189,7 +206,7 @@ REGISTER_CMD=$(cat <<EOF if [ "\$FORMAT" != "raw" ]; then $QEMU_IMG convert -O raw $TMP_DST $DEV else - dd \if=$TMP_DST of=$DEV bs=${DD_BLOCK_SIZE:-64k} + dd \if=$TMP_DST of=$DEV bs=${DD_BLOCK_SIZE:-64k} oflag=direct fi # remove original @@ -214,4 +231,4 @@ ssh_exec_and_log "$DST_HOST" "$FLUSH_CMD" \ python ${DRIVER_PATH}/3par.py unexportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $DST_HOST -echo $NAME_WWN \ No newline at end of file +echo "$NAME_WWN raw" diff --git a/datastore/3par/discover_lun.sh b/datastore/3par/discover_lun.sh new file mode 100755 index 0000000000000000000000000000000000000000..393a2f3895bfd9856284b2aa7fb33ed8cd01c97f --- /dev/null +++ b/datastore/3par/discover_lun.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +SCRIPT_PATH=$(dirname $0) + +. ${SCRIPT_PATH}/scripts_3par.sh + +# Log function that knows how to deal with severities and adds the +# script name +function log_function +{ + echo "$1: $SCRIPT_NAME: $2" 1>&2 +} + +# Logs an info message +function log_info +{ + log_function "INFO" "$1" +} + +# Logs an error message +function log_error +{ + log_function "ERROR" "$1" +} + +# Logs a debug message +function log_debug +{ + log_function "DEBUG" "$1" +} + +# This function is used to pass error message to the mad +function error_message +{ + ( + echo "ERROR MESSAGE --8<------" + echo "$1" + echo "ERROR MESSAGE ------>8--" + ) 1>&2 +} + +# Executes a command, if it fails returns error message and exits +# If a second parameter is present it is used as the error message when +# the command fails +function exec_and_log +{ + EXEC_LOG_ERR=`bash -s 2>&1 1>/dev/null <<EOF +export LANG=C +export LC_ALL=C +$1 +EOF` + EXEC_LOG_RC=$? + + if [ $EXEC_LOG_RC -ne 0 ]; then + log_error "Command \"$1\" failed: $EXEC_LOG_ERR" + + if [ -n "$2" ]; then + error_message "$2" + else + error_message "Error executing $1: $EXEC_LOG_ERR" + fi + exit $EXEC_LOG_RC + fi +} + + +LUN=$1 +WWN=$2 + +DISCOVER_CMD=$(cat <<EOF + set -e + $(discover_lun "$LUN" "$WWN") +EOF +) + +exec_and_log "$DISCOVER_CMD" \ + "Error discovering LUN $LUN:$WWN" + +exit 0 diff --git a/datastore/3par/flush_lun.sh b/datastore/3par/flush_lun.sh new file mode 100755 index 0000000000000000000000000000000000000000..eb8ba5e40a09b8c7a8e26a45c2bfac42abcadc89 --- /dev/null +++ b/datastore/3par/flush_lun.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +SCRIPT_PATH=$(dirname $0) + +. ${SCRIPT_PATH}/scripts_3par.sh + +# Log function that knows how to deal with severities and adds the +# script name +function log_function +{ + echo "$1: $SCRIPT_NAME: $2" 1>&2 +} + +# Logs an info message +function log_info +{ + log_function "INFO" "$1" +} + +# Logs an error message +function log_error +{ + log_function "ERROR" "$1" +} + +# Logs a debug message +function log_debug +{ + log_function "DEBUG" "$1" +} + +# This function is used to pass error message to the mad +function error_message +{ + ( + echo "ERROR MESSAGE --8<------" + echo "$1" + echo "ERROR MESSAGE ------>8--" + ) 1>&2 +} + +# Executes a command, if it fails returns error message and exits +# If a second parameter is present it is used as the error message when +# the command fails +function exec_and_log +{ + EXEC_LOG_ERR=`bash -s 2>&1 1>/dev/null <<EOF +export LANG=C +export LC_ALL=C +$1 +EOF` + EXEC_LOG_RC=$? + + if [ $EXEC_LOG_RC -ne 0 ]; then + log_error "Command \"$1\" failed: $EXEC_LOG_ERR" + + if [ -n "$2" ]; then + error_message "$2" + else + error_message "Error executing $1: $EXEC_LOG_ERR" + fi + exit $EXEC_LOG_RC + fi +} + + +WWN=$1 + +FLUSH_CMD=$(cat <<EOF + set -e + $(remove_lun "$WWN") +EOF +) + +exec_and_log "$FLUSH_CMD" \ + "Error flushing LUN $WWN" + +exit 0 diff --git a/datastore/3par/mkfs b/datastore/3par/mkfs index 1c7f80f72d263c68c79bd244cc43e8429472fb4f..9e8897abc613fefe0c0c25039121953525f1c66f 100644 --- a/datastore/3par/mkfs +++ b/datastore/3par/mkfs @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -33,7 +33,9 @@ fi . $LIB_LOCATION/sh/scripts_common.sh DRIVER_PATH=$(dirname $0) +source ${DRIVER_PATH}/../libfs.sh source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf +. ${DRIVER_PATH}/scripts_3par.sh # -------- Get mkfs and datastore arguments from OpenNebula core ------------ @@ -42,29 +44,119 @@ ID=$2 XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG \ +done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/REMOTE_COPY \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_CPG \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/THIN \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/DEDUP \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/COMPRESSION \ - /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE \ /DS_DRIVER_ACTION_DATA/IMAGE/NAME \ - /DS_DRIVER_ACTION_DATA/IMAGE/SIZE) - -unset i + /DS_DRIVER_ACTION_DATA/IMAGE/PERSISTENT \ + /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ + /DS_DRIVER_ACTION_DATA/IMAGE/FS) -CPG="${XPATH_ELEMENTS[i++]:-$CPG}" -THIN="${XPATH_ELEMENTS[i++]:-$THIN}" -DEDUP="${XPATH_ELEMENTS[i++]:-$DEDUP}" -COMPRESSION="${XPATH_ELEMENTS[i++]:-$COMPRESSION}" -NAMING_TYPE="${XPATH_ELEMENTS[i++]:-$NAMING_TYPE}" -NAME="${XPATH_ELEMENTS[i++]}" -SIZE="${XPATH_ELEMENTS[i++]:-0}" +DSID="${XPATH_ELEMENTS[j++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" +THIN="${XPATH_ELEMENTS[j++]:-$THIN}" +DEDUP="${XPATH_ELEMENTS[j++]:-$DEDUP}" +COMPRESSION="${XPATH_ELEMENTS[j++]:-$COMPRESSION}" +NAME="${XPATH_ELEMENTS[j++]}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" +SIZE="${XPATH_ELEMENTS[j++]:-0}" +FSTYPE="${XPATH_ELEMENTS[j++]}" # ------------ Create image ------------- -python ${DRIVER_PATH}/3par.py createVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE \ - -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION -id $ID -c $CPG -sz $SIZE -co "$NAME" \ No newline at end of file +NAME_WWN=$(python ${DRIVER_PATH}/3par.py createVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE \ + -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION -id $ID -c $CPG -sz $SIZE -co "$NAME") + +if [ $? -ne 0 ]; then + error_message "$NAME_WWN" + exit 1 +fi + +NAME=$(get_vv_name "$NAME_WWN") +WWN=$(get_vv_wwn "$NAME_WWN") + +# ----------- Add image to RC Group ------- +# only non-persistent images +if [ "$PERSISTENT" == "0" ] && [ "$REMOTE_COPY" == "YES" ]; then + log "Add image to remote copy group" + RCG=$(python ${DRIVER_PATH}/3par.py addVolumeToRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME \ + -c $CPG -sc $SEC_CPG -rcm $REMOTE_COPY_MODE -rcgn "$NAMING_TYPE.one.ds.$DSID" -rcha "NO") + + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi +fi + +# Filesystem defined, so we need format disk +if [ -n "$FSTYPE" ]; then + if [ "$FSTYPE" == "xfs" ] || [ "$FSTYPE" == "ext4" ] || [ "$FSTYPE" == "ext3" ] || [ "$FSTYPE" == "ext2" ]; then + DST_HOST=`get_destination_host $ID` + + if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit 1 + fi + + # Map image + log "Mapping $WWN to $DST_HOST" + + LUN=$(python ${DRIVER_PATH}/3par.py exportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $DST_HOST) + + if [ $? -ne 0 ]; then + error_message "$LUN" + exit 1 + fi + + log "Discover $WWN and format it to fs: $FSTYPE" + DISCOVER_CMD=$(cat <<EOF + set -e + $(discover_lun "$LUN" "$WWN") + + sudo /usr/sbin/mkfs -t "$FSTYPE" "\$DEV" +EOF +) + + ssh_exec_and_log "$DST_HOST" "$DISCOVER_CMD" \ + "Error registering $WWN to $DST_HOST" + + # Unmap image + log "Unmapping $WWN from $DST_HOST" + + FLUSH_CMD=$(cat <<EOF + set -e + $(remove_lun "$WWN") +EOF +) + + ssh_exec_and_log "$DST_HOST" "$FLUSH_CMD" \ + "Error flushing out mapping" + + python ${DRIVER_PATH}/3par.py unexportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $DST_HOST + fi +fi + +echo $NAME_WWN diff --git a/datastore/3par/monitor b/datastore/3par/monitor index a5629118cf843035f49891a70adf1ffe7a81f385..b4c18a9c635a9845f45c2b16311395b09fb86d2d 100644 --- a/datastore/3par/monitor +++ b/datastore/3par/monitor @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -44,13 +44,17 @@ ID=$2 XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG) +done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG) -CPG="${XPATH_ELEMENTS[0]:-$CPG}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" # ------------ Compute datastore usage ------------- diff --git a/datastore/3par/rm b/datastore/3par/rm index 7732bfe31082c65d6649fa2ebf03ff1a7b0ee15f..94151b8543a34a3419141481bcb2b2ca5113aff8 100644 --- a/datastore/3par/rm +++ b/datastore/3par/rm @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -34,12 +34,85 @@ fi DRIVER_PATH=$(dirname $0) source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf +. ${DRIVER_PATH}/scripts_3par.sh # -------- Get rm and datastore arguments from OpenNebula core ------------ DRV_ACTION=$1 ID=$2 -# ------------ Delete image ------------- +XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -python ${DRIVER_PATH}/3par.py deleteVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $ID \ No newline at end of file +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH /DS_DRIVER_ACTION_DATA/IMAGE/PERSISTENT \ + /DS_DRIVER_ACTION_DATA/IMAGE/SOURCE \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC_SYSTEM_DS_ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/REMOTE_COPY) + +PERSISTENT="${XPATH_ELEMENTS[j++]}" +SOURCE="${XPATH_ELEMENTS[j++]}" +SYS_DS_REMOTE_COPY="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" +DSID="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + +# only non-persistent images +if [ "$PERSISTENT" == "0" ] && [ "$REMOTE_COPY" == "YES" ]; then + NAME=$(get_vv_name "$SOURCE") + + log "Remove disk from Remote Copy group" + RCG=$(python ${DRIVER_PATH}/3par.py deleteVolumeFromRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -rcgn "$NAMING_TYPE.one.ds.$DSID") + + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi + + # Delete image from both 3par systems + python ${DRIVER_PATH}/3par.py deleteVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $ID -rc $REMOTE_COPY + + exit $? +fi + +# persistent disks deployed in RC enabled system datastore +if [ "$SYS_DS_REMOTE_COPY" == "YES" ]; then + #------------------------------------------------------------------------------- + # Get system ds information + #------------------------------------------------------------------------------- + + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP) + + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + + # Delete image from both 3par systems + python ${DRIVER_PATH}/3par.py deleteVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $ID -rc $SYS_DS_REMOTE_COPY + + exit $? +fi + +# Delete image from single 3par system +python ${DRIVER_PATH}/3par.py deleteVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -nt $NAMING_TYPE -id $ID diff --git a/datastore/3par/scripts_3par.sh b/datastore/3par/scripts_3par.sh index c73317408abfea280c5cdde613b099c401362efd..6403133874cbb7a00b3b38d5ee9e3f1812417efb 100644 --- a/datastore/3par/scripts_3par.sh +++ b/datastore/3par/scripts_3par.sh @@ -1,6 +1,6 @@ #@IgnoreInspection BashAddShebang # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # # @@ -24,9 +24,17 @@ MULTIPATHD=multipathd TEE=tee BASENAME=basename +if [ -z "${ONE_LOCATION}" ]; then + PY3PAR="python /var/lib/one/remotes/datastore/3par/3par.py" + XPATH="/var/lib/one/remotes/datastore/xpath.rb --stdin" +else + PY3PAR="python $ONE_LOCATION/var/remotes/datastore/3par/3par.py" + XPATH="$ONE_LOCATION/var/remotes/datastore/xpath.rb --stdin" +fi + function multipath_flush { - local MAP_NAME - MAP_NAME="$1" + local MAP_NAME="$1" + echo "$SUDO $MULTIPATH -f $MAP_NAME" } @@ -36,15 +44,15 @@ function multipath_rescan { } function multipath_resize { - local MAP_NAME - MAP_NAME="$1" + local MAP_NAME="$1" + echo "$SUDO $MULTIPATHD -k\"resize map $MAP_NAME\"" } function rescan_scsi_bus { - local LUN + local LUN="$1" local FORCE - LUN="$1" + # important to ignore rev, otherwise rescan failed when 3PAR OS get major update and device is online resized # https://gitlab.feldhost.cz/feldhost-public/one-addon-3par/-/issues/1 [ "$2" == "force" ] && FORCE=" --forcerescan --ignore-rev" @@ -53,17 +61,39 @@ function rescan_scsi_bus { } function get_vv_name { - local NAME_WWN - NAME_WWN="$1" + local NAME_WWN="$1" + echo "$NAME_WWN" | $AWK -F: '{print $1}' } function get_vv_wwn { - local NAME_WWN - NAME_WWN="$1" + local NAME_WWN="$1" + echo "$NAME_WWN" | $AWK -F: '{print $2}' } +function image_update { + local ID="$1" + local DATA="$2" + + _TEMPLATE=$(mktemp -t oneimageUpdate-XXXXXXXXXX) + trap "rm -f \"$_TEMPLATE\"" TERM INT QUIT HUP EXIT + echo $DATA > $_TEMPLATE + + oneimage update $ID -a $_TEMPLATE +} + +function get_image_running_vms_count { + local IMAGE_ID="$1" + local i XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(oneimage show -x "$IMAGE_ID" | $XPATH /IMAGE/RUNNING_VMS) + + echo "${XPATH_ELEMENTS[0]}" +} + function discover_lun { local LUN local WWN @@ -71,7 +101,6 @@ function discover_lun { WWN="$2" cat <<EOF $(rescan_scsi_bus "$LUN") - $(multipath_rescan) DEV="/dev/mapper/3$WWN" @@ -81,17 +110,10 @@ function discover_lun { sleep 1 COUNTER=\$((\$COUNTER + 1)) done - if [ ! -e \$DEV ]; then - # Last chance to get our mapping - $(multipath_rescan) - COUNTER=1 - while [ ! -e "\$DEV" ] && [ \$COUNTER -le 10 ]; do - sleep 1 - COUNTER=\$((\$COUNTER + 1)) - done - fi + # Exit with error if mapping does not exist if [ ! -e \$DEV ]; then + echo 'Mapping does not exists' exit 1 fi @@ -105,6 +127,7 @@ function discover_lun { done # Exit with error if mapping has no path if [ ! "\${DM_SLAVE}" ]; then + echo 'Mapping has no path' exit 1 fi EOF @@ -115,18 +138,338 @@ function remove_lun { WWN="$1" cat <<EOF DEV="/dev/mapper/3$WWN" - DM_HOLDER=\$($SUDO $DMSETUP ls -o blkdevname | grep -Po "(?<=3$WWN\s\()[^)]+") - DM_SLAVE=\$(ls /sys/block/\${DM_HOLDER}/slaves) + SLAVES=\$($SUDO $MULTIPATH -l 3$WWN | grep -Eo 'sd[a-z]+') - $(multipath_flush "\$DEV") + if [ -z "\${SLAVES}" ]; then + SLAVES=\$($SUDO $MULTIPATH -r 3$WWN | grep -Eo 'sd[a-z]+') + fi + + $(multipath_flush "3$WWN") unset device - for device in \${DM_SLAVE} + for device in \${SLAVES} do if [ -e /dev/\${device} ]; then $SUDO $BLOCKDEV --flushbufs /dev/\${device} echo 1 | $SUDO $TEE /sys/block/\${device}/device/delete fi done + + # wait for auto remove multipath + EXISTS=1 + COUNTER=1 + while [ "\${SLAVES}" ] && [ \$EXISTS -gt 0 ] && [ \$COUNTER -le 30 ]; do + sleep 1 + EXISTS=\$($SUDO $MULTIPATH -ll 3$WWN | head -c1 | wc -c) + COUNTER=\$((\$COUNTER + 1)) + done + + if [[ \$EXISTS -gt 0 ]]; then + # Wait for releasing device + OPEN_COUNT=1 + while [ \$OPEN_COUNT -gt 0 ]; do + sleep 1 + OPEN_COUNT=\$($SUDO $DMSETUP info 3$WWN | grep -P "Open\scount:" | grep -oP "[0-9]+") + done + + $(multipath_flush "3$WWN") + fi +EOF +} + +function unmap_lun { + local HOST + local WWN + HOST="$1" + WWN="$2" + local CMD + + log "Unmapping $WWN from $HOST" + + CMD=$(cat <<EOF + set -e + $(remove_lun "$WWN") +EOF +) + + ssh_exec_and_log "$HOST" "$CMD" \ + "Error flushing out mapping" +} + +function rescan_lun { + local HOST="$1" + local LUN="$2" + local CMD + + CMD=$(cat <<EOF + set -e + $(rescan_scsi_bus "$LUN") +EOF +) + + ssh_exec_and_log "$HOST" "$CMD" \ + "Error registering remote $LUN to $HOST" +} + +function map_lun { + local HOST="$1" + local LUN="$2" + local WWN="$3" + local DST_DIR="$4" + local DST_PATH="$5" + local CMD + + CMD=$(cat <<EOF + set -e + mkdir -p "$DST_DIR" + $(discover_lun "$LUN" "$WWN") + ln -sf "\$DEV" "$DST_PATH" +EOF +) + + ssh_exec_and_log "$HOST" "$CMD" \ + "Error registering $WWN to $HOST" +} + +function map_and_copy_to_lun { + local HOST="$1" + local SRC_WWN="$2" + local LUN="$3" + local WWN="$4" + local DEV SRC_DEV CMD + + SRC_DEV="/dev/mapper/3$SRC_WWN" + DEV="/dev/mapper/3$WWN" + + CMD=$(cat <<EOF + set -e + $(discover_lun "$LUN" "$WWN") EOF +) + + log "Map $WWN to $HOST" + ssh_exec_and_log "$HOST" "$CMD" \ + "Error mapping $WWN to $HOST" + + + CMD=$(cat <<EOF + set -e -o pipefail + dd \if=$SRC_DEV of=$DEV bs=${DD_BLOCK_SIZE:-64k} oflag=direct +EOF +) + + log "Copy $SRC_WWN to $WWN" + ssh_exec_and_log "$HOST" "$CMD" \ + "Error copy from $SRC_WWN to $WWN" +} + +function unexport_vv { + local NAME="$1" + local HOST="$2" + local RC="${3:-NO}" + local RESULT + + if [[ "$RC" == "YES" ]]; then + log "Unexporting remote $NAME from $HOST" + RESULT=$($PY3PAR unexportVV -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" -n "$NAME" \ + -hs "$HOST" -sapi "$SEC_API_ENDPOINT" -sip "$SEC_IP" -rc "$RC") + else + log "Unexporting $NAME from $HOST" + RESULT=$($PY3PAR unexportVV -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" -n "$NAME" \ + -hs "$HOST") + fi + + if [ $? -ne 0 ]; then + error_message "Error unexporting VV: $RESULT" + kill -s TERM $TOP_PID + fi } + +function export_vv { + local NAME="$1" + local HOST="$2" + local RC="${3:-NO}" + local LUN + + if [[ "$RC" == "YES" ]]; then + log "Mapping remote $NAME to $HOST" + LUN=$($PY3PAR exportVV -a "$API_ENDPOINT" -i "$IP" -sapi "$SEC_API_ENDPOINT" -sip "$SEC_IP" -s "$SECURE" \ + -u "$USERNAME" -p "$PASSWORD" -n "$NAME" -hs "$HOST" -rc "YES") + else + log "Mapping $NAME to $HOST" + LUN=$($PY3PAR exportVV -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" -n "$NAME" \ + -hs "$HOST") + fi + + if [ $? -ne 0 ]; then + error_message "$LUN" + kill -s TERM $TOP_PID + fi + + echo "$LUN" +} + +function delete_vm_clone_vv { + local API_ENDPOINT="$1" + local IP="$2" + local VMID="$3" + local DISK_ID="$4" + local DEL + + DEL=$($PY3PAR deleteVmClone -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" \ + -nt "$NAMING_TYPE" -vi "$VMID" -id "$DISK_ID") + + if [ $? -ne 0 ]; then + error_message "$DEL" + kill -s TERM $TOP_PID + fi +} + +function host_exists { + local HOST="$1" + local HOST_3PAR + + HOST_3PAR=$($PY3PAR hostExists -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" -hs "$HOST") + + if [ $? -ne 0 ]; then + error_message "$HOST_3PAR" + kill -s TERM $TOP_PID + fi + + echo "$HOST_3PAR" +} + +function remove_vv_from_rcg { + local NAME="$1" + local VMID="$2" + local RCG + + log "Remove disk from Remote Copy group" + + RCG=$($PY3PAR deleteVolumeFromRCGroup -a "$API_ENDPOINT" -i "$IP" -sapi "$SEC_API_ENDPOINT" -sip "$SEC_IP" \ + -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" -nt "$NAMING_TYPE" -n "$NAME" -vi "$VMID") + + if [ $? -ne 0 ]; then + error_message "$RCG" + kill -s TERM $TOP_PID + fi +} + +function add_vv_to_rcg { + local NAME="$1" + local VMID="$2" + local RCG + + log "Create remote copy group" + + RCG=$($PY3PAR addVolumeToRCGroup -a "$API_ENDPOINT" -i "$IP" -sapi "$SEC_API_ENDPOINT" -sip "$SEC_IP" -s "$SECURE" \ + -u "$USERNAME" -p "$PASSWORD" -nt "$NAMING_TYPE" -c "$CPG" -sc "$SEC_CPG" -rcm "$REMOTE_COPY_MODE" \ + -n "$NAME" -vi "$VMID") + + if [ $? -ne 0 ]; then + error_message "$RCG" + kill -s TERM $TOP_PID + fi + + log "$RCG" +} + +function remove_vv_from_vvset { + local NAME="$1" + local VMID="$2" + local VVSET + + log "Remove disk from VM VV Set" + VVSET=$($PY3PAR deleteVolumeFromVVSet -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" \ + -nt "$NAMING_TYPE" -n "$NAME" -vi "$VMID") + + if [ $? -ne 0 ]; then + error_message "$VVSET" + kill -s TERM $TOP_PID + fi +} + +function add_vv_to_vvset { + local NAME="$1" + local VMID="$2" + local VVSET + + log "Add disk to VM VV Set" + VVSET=$($PY3PAR addVolumeToVVSet -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" \ + -nt "$NAMING_TYPE" -n "$NAME" -vi "$VMID") + + if [ $? -ne 0 ]; then + error_message "$VVSET" + kill -s TERM $TOP_PID + fi +} + +function get_vm_clone_vv_source { + local API_ENDPOINT="$1" + local IP="$2" + local VMID="$3" + local DISK_ID="$4" + local NAME_WWN + + NAME_WWN=$($PY3PAR getVmClone -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" \ + -nt "$NAMING_TYPE" -vi "$VMID" -id "$DISK_ID") + + if [ $? -ne 0 ]; then + error_message "$NAME_WWN" + kill -s TERM $TOP_PID + fi + + echo "$NAME_WWN" +} + +function create_qos_policy { + local VMID="$1" + local QOS + + log "Create QoS Policy" + QOS=$($PY3PAR createQosPolicy -a "$API_ENDPOINT" -i "$IP" -sapi "$SEC_API_ENDPOINT" -sip "$SEC_IP" -s "$SECURE" \ + -u "$USERNAME" -p "$PASSWORD" -nt "$NAMING_TYPE" -qp "$QOS_PRIORITY" -qxi "$QOS_MAX_IOPS" -qmi "$QOS_MIN_IOPS" \ + -qxb "$QOS_MAX_BW" -qmb "$QOS_MIN_BW" -ql "$QOS_LATENCY" -rc "$REMOTE_COPY" -vi "$VMID") + + if [ $? -ne 0 ]; then + error_message "$QOS" + kill -s TERM $TOP_PID + fi +} + +function disable_qos_policy { + local API_ENDPOINT="$1" + local IP="$2" + local VMID="$3" + local QOS + + log "Disable QoS Policy" + QOS=$($PY3PAR disableQosPolicy -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" \ + -nt "$NAMING_TYPE" -vi "$VMID") + + if [ $? -ne 0 ]; then + error_message "$QOS" + kill -s TERM $TOP_PID + fi +} + +function create_vm_clone_vv { + local SRC_NAME="$1" + local VMID="$2" + local DISK_ID="$3" + local SIZE="$4" + local COMMENT="$5" + local EMPTY="$6" + local NAME_WWN + + NAME_WWN=$($PY3PAR createVmClone -a "$API_ENDPOINT" -i "$IP" -s "$SECURE" -u "$USERNAME" -p "$PASSWORD" \ + -nt "$NAMING_TYPE" -tpvv "$THIN" -tdvv "$DEDUP" -compr "$COMPRESSION" -sn "$SRC_NAME" -vi "$VMID" \ + -id "$DISK_ID" -c "$CPG" -sz "$SIZE" -co "$COMMENT" -e "$EMPTY") + + if [ $? -ne 0 ]; then + error_message "$NAME_WWN" + kill -s TERM $TOP_PID + fi + + echo "$NAME_WWN" +} \ No newline at end of file diff --git a/datastore/3par/snap_delete b/datastore/3par/snap_delete index 89c1c372c319890bc91abc5db5642c44207c4c1f..e3795fc90f43f99eb603e876ca782280112b58ac 100644 --- a/datastore/3par/snap_delete +++ b/datastore/3par/snap_delete @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -44,17 +44,41 @@ ID=$2 XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <($XPATH /DS_DRIVER_ACTION_DATA/IMAGE/TARGET_SNAPSHOT \ - /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE) + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC_SYSTEM_DS_ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP) -unset i +SNAP_ID="${XPATH_ELEMENTS[j++]}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" -SNAP_ID="${XPATH_ELEMENTS[i++]}" -NAMING_TYPE="${XPATH_ELEMENTS[i++]:-$NAMING_TYPE}" +if [ "$REMOTE_COPY" == "YES" ]; then + #------------------------------------------------------------------------------- + # Get system ds information + #------------------------------------------------------------------------------- -python ${DRIVER_PATH}/3par.py deleteSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE \ - -id $ID -si $SNAP_ID \ No newline at end of file + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP) + + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + + python ${DRIVER_PATH}/3par.py deleteSnapshot -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $ID -si $SNAP_ID -rc $REMOTE_COPY +else + python ${DRIVER_PATH}/3par.py deleteSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -nt $NAMING_TYPE -id $ID -si $SNAP_ID +fi diff --git a/datastore/3par/snap_flatten b/datastore/3par/snap_flatten index e572138deeb017e972e0cefde6f2e08ac8590574..539d57583f190aad86587b71b08ae1c4ed6fa45a 100644 --- a/datastore/3par/snap_flatten +++ b/datastore/3par/snap_flatten @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -44,18 +44,45 @@ ID=$2 XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <($XPATH /DS_DRIVER_ACTION_DATA/IMAGE/TARGET_SNAPSHOT \ - /DS_DRIVER_ACTION_DATA/IMAGE/SOURCE) + /DS_DRIVER_ACTION_DATA/IMAGE/SOURCE \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC_SYSTEM_DS_ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP) + +SNAP_ID="${XPATH_ELEMENTS[j++]}" +SOURCE="${XPATH_ELEMENTS[j++]}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" -unset i +NAME=$(get_vv_name "$SOURCE") -SNAP_ID="${XPATH_ELEMENTS[i++]}" -SOURCE="${XPATH_ELEMENTS[i++]}" +if [ "$REMOTE_COPY" == "YES" ]; then + #------------------------------------------------------------------------------- + # Get system ds information + #------------------------------------------------------------------------------- -NAME=$(get_vv_name "$SOURCE") + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP) -python ${DRIVER_PATH}/3par.py flattenSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -sn $NAME -si $SNAP_ID \ No newline at end of file + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + + python ${DRIVER_PATH}/3par.py flattenSnapshot -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -sn $NAME -si $SNAP_ID -rc $REMOTE_COPY +else + python ${DRIVER_PATH}/3par.py flattenSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -sn $NAME -si $SNAP_ID +fi diff --git a/datastore/3par/snap_revert b/datastore/3par/snap_revert index 8708a84551a3631d0395b5184620cc5c1cf71ee3..88efc0333beeb7e70a07c2fc4331f2c3f6933c35 100644 --- a/datastore/3par/snap_revert +++ b/datastore/3par/snap_revert @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -44,17 +44,41 @@ ID=$2 XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <($XPATH /DS_DRIVER_ACTION_DATA/IMAGE/TARGET_SNAPSHOT \ - /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE) + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/RC_SYSTEM_DS_ID \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP) -unset i +SNAP_ID="${XPATH_ELEMENTS[j++]}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" -SNAP_ID="${XPATH_ELEMENTS[i++]}" -NAMING_TYPE="${XPATH_ELEMENTS[i++]:-$NAMING_TYPE}" +if [ "$REMOTE_COPY" == "YES" ]; then + #------------------------------------------------------------------------------- + # Get system ds information + #------------------------------------------------------------------------------- -python ${DRIVER_PATH}/3par.py revertSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE \ - -id $ID -si $SNAP_ID \ No newline at end of file + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP) + + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + + python ${DRIVER_PATH}/3par.py revertSnapshot -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $ID -si $SNAP_ID -rc $REMOTE_COPY -off 1 +else + python ${DRIVER_PATH}/3par.py revertSnapshot -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $ID -si $SNAP_ID -off 1 +fi diff --git a/etc/datastore/3par/3par.conf b/etc/datastore/3par/3par.conf index 85d967ab71429f509c3de7974b2d1f3685476c9b..428dafeb67cde7fc279a9780b2ec107d7d9472a5 100644 --- a/etc/datastore/3par/3par.conf +++ b/etc/datastore/3par/3par.conf @@ -1,5 +1,5 @@ # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # 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 # @@ -18,7 +18,9 @@ DD_BLOCK_SIZE=64k # 3PAR WSAPI Endpoint +# Secondary 3PAR WSAPI Endpoint (used for Remote Copy) API_ENDPOINT="http://{IP}:8008/api/v1" +SEC_API_ENDPOINT="http://{SEC_IP}:8008/api/v1" # Only valid SSL certificates # SSL certification verification is defaulted to False. In order to @@ -26,14 +28,25 @@ API_ENDPOINT="http://{IP}:8008/api/v1" SECURE=NO # 3PAR IP address for SSH authentication options for the SSH based calls +# Secondary 3PAR IP address (used for Remote Copy) IP="{IP}" +SEC_IP="{SEC_IP}" # 3PAR username and password USERNAME="{USERNAME}" PASSWORD="{PASSWORD}" +# Enable Remote Copy +REMOTE_COPY=NO + +# Remote Copy mode +# SYNC|PERIODIC|ASYNC +REMOTE_COPY_MODE="SYNC" + # Default CPG to use. Can be overwritten in datastore template +# Default CPG to use on secondary system CPG="SSD_r6" +SEC_CPG="SSD_r6" # Use of thin volumes. By default enabled. You need thin provisioning license # Configurable in datastore template diff --git a/hooks/3par/image_persistent.sh b/hooks/3par/image_persistent.sh new file mode 100644 index 0000000000000000000000000000000000000000..41fb68dbfed62a0966c40d4a57f4f1083cc22e8c --- /dev/null +++ b/hooks/3par/image_persistent.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +# -------------------------------------------------------------------------- # +# Copyright 2022, FeldHost™ (feldhost.net) # +# # +# Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # +# # +# 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. # +# -------------------------------------------------------------------------- # + +############################################################################### +# This script should be called using hook when image persistent state is changed +############################################################################### + +# Hook template +# NAME = 3par-image-persistent +# TYPE = api +# COMMAND = "3par/image_persistent.sh" +# ARGUMENTS = $API +# CALL = "one.image.persistent" + + +# ------------ Set up the environment to source common tools ------------ + +if [ -z "${ONE_LOCATION}" ]; then + TMCOMMON=/var/lib/one/remotes/tm/tm_common.sh +else + TMCOMMON=$ONE_LOCATION/var/remotes/tm/tm_common.sh +fi + +. $TMCOMMON + +DRIVER_PATH=$(dirname $0) + +source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf +. ${DRIVER_PATH}/../../datastore/3par/scripts_3par.sh + +# -------- Get template argument from OpenNebula core ------------ + +API=$1 + +#------------------------------------------------------------------------------- +# Get dest ds information +#------------------------------------------------------------------------------- + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb -b $API" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH /CALL_INFO/PARAMETERS/PARAMETER[2]/VALUE \ + /CALL_INFO/PARAMETERS/PARAMETER[3]/VALUE \ + /CALL_INFO/PARAMETERS/PARAMETER[4]/VALUE) + +IMAGE_ID="${XPATH_ELEMENTS[j++]}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" +SUCCESS="${XPATH_ELEMENTS[j++]}" + +# api call for changing image persistant state was successfull +if [ "$SUCCESS" == "true" ]; then + XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + + #------------------------------------------------------------------------------- + # Get image datastore id and source + #------------------------------------------------------------------------------- + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(oneimage show -x $IMAGE_ID| $XPATH /IMAGE/DATASTORE_ID) + + DSID="${XPATH_ELEMENTS[j++]}" + + #------------------------------------------------------------------------------- + # Get image ds information + #------------------------------------------------------------------------------- + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ + /DATASTORE/TEMPLATE/NAMING_TYPE \ + /DATASTORE/TEMPLATE/CPG \ + /DATASTORE/TEMPLATE/SEC_CPG) + + API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" + IP="${XPATH_ELEMENTS[j++]:-$IP}" + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" + CPG="${XPATH_ELEMENTS[j++]:-$CPG}" + SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" + + if [ "$REMOTE_COPY" == "YES" ]; then + NAME="$NAMING_TYPE.one.$IMAGE_ID.vv" + + if [ "$PERSISTENT" == "0" ]; then + log "Add image to remote copy group" + RCG=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py addVolumeToRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME \ + -c $CPG -sc $SEC_CPG -rcm $REMOTE_COPY_MODE -rcgn "$NAMING_TYPE.one.ds.$DSID" -rcha "NO") + + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi + else + log "Remove disk from Remote Copy group" + RCG=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME \ + -rcgn "$NAMING_TYPE.one.ds.$DSID") + + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi + + log "Remove disk from remote system" + DEL=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVV -a $SEC_API_ENDPOINT -i $SEC_IP \ + -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $IMAGE_ID) + + if [ $? -ne 0 ]; then + error_message "$DEL" + exit 1 + fi + fi + fi +fi diff --git a/install.sh b/install.sh index 3d050ccb18e57044acbfe969b67958f524f8f185..e7c0775a4e8f2c51d26762640878e80ef01d23d7 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2015-2018, Storpool (storpool.com) # # # diff --git a/scripts/install-5.6.0.sh b/scripts/install-5.6.0.sh index 789df434251b3edfd0fccb61c0c45dcdfd1ec824..b3fd86babe7c7ab776272c7431e7a74f0e3edff6 100755 --- a/scripts/install-5.6.0.sh +++ b/scripts/install-5.6.0.sh @@ -20,8 +20,8 @@ end_msg= -# install datastore and tm MAD -for MAD in datastore tm; do +# install datastore and tm MAD, hooks +for MAD in datastore tm hooks; do M_DIR="${ONE_VAR}/remotes/${MAD}" echo "*** Installing ${M_DIR}/3par ..." mkdir -pv "${M_DIR}/3par" @@ -35,6 +35,23 @@ cp $CP_ARG "$CWD/vmm/kvm/"snapshot_*-3par "${ONE_VAR}/remotes/vmm/kvm/" chmod a+x "${ONE_VAR}/remotes/vmm/kvm/"snapshot_*-3par chown oneadmin: "${ONE_VAR}/remotes/vmm/kvm/"snapshot_*-3par +echo "*** Copy VMM deploy and attach_disk scripts to ${ONE_VAR}/remotes/vmm/kvm/ ..." +cp $CP_ARG "$CWD/vmm/kvm/deploy" "${ONE_VAR}/remotes/vmm/kvm/" +cp $CP_ARG "$CWD/vmm/kvm/attach_disk" "${ONE_VAR}/remotes/vmm/kvm/" +cp $CP_ARG "$CWD/vmm/kvm/restore" "${ONE_VAR}/remotes/vmm/kvm/" +chmod a+x "${ONE_VAR}/remotes/vmm/kvm/deploy" +chmod a+x "${ONE_VAR}/remotes/vmm/kvm/attach_disk" +chmod a+x "${ONE_VAR}/remotes/vmm/kvm/restore" +chown oneadmin: "${ONE_VAR}/remotes/vmm/kvm/deploy" +chown oneadmin: "${ONE_VAR}/remotes/vmm/kvm/attach_disk" +chown oneadmin: "${ONE_VAR}/remotes/vmm/kvm/restore" + +echo "*** Copy checkMultipath.py and dmmp.py scripts to ${ONE_VAR}/remotes/vmm/ ..." +cp $CP_ARG "$CWD/vmm/checkMultipath.py" "${ONE_VAR}/remotes/vmm/" +cp $CP_ARG "$CWD/vmm/dmmp.py" "${ONE_VAR}/remotes/vmm/" +chown oneadmin: "${ONE_VAR}/remotes/vmm/checkMultipath.py" +chown oneadmin: "${ONE_VAR}/remotes/vmm/dmmp.py" + # Enable 3PAR in oned.conf if grep -q -i 3par /etc/one/oned.conf >/dev/null 2>&1; then echo "*** 3PAR is already enabled in /etc/one/oned.conf" diff --git a/scripts/install-6.2.2.sh b/scripts/install-6.2.2.sh new file mode 120000 index 0000000000000000000000000000000000000000..e2b366e8a52d1fafd4c9b8f18bc25a3865b22ab1 --- /dev/null +++ b/scripts/install-6.2.2.sh @@ -0,0 +1 @@ +install-5.6.0.sh \ No newline at end of file diff --git a/scripts/install-6.4.1.sh b/scripts/install-6.4.1.sh new file mode 120000 index 0000000000000000000000000000000000000000..e2b366e8a52d1fafd4c9b8f18bc25a3865b22ab1 --- /dev/null +++ b/scripts/install-6.4.1.sh @@ -0,0 +1 @@ +install-5.6.0.sh \ No newline at end of file diff --git a/tm/3par/clone b/tm/3par/clone index 2d98157f38567e66a80bc06265e99c4e77b043ff..e917a608cc679b05a46c04c3b79c183adb7aad57 100644 --- a/tm/3par/clone +++ b/tm/3par/clone @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -79,7 +79,7 @@ done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/ORIGINAL_SIZE \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -VM_NAME="${XPATH_ELEMENTS[j++]}" +VM_NAME="${XPATH_ELEMENTS[j++]//[^A-Za-z0-9\[\]() _~+-]/}" SIZE="${XPATH_ELEMENTS[j++]}" ORIGINAL_SIZE="${XPATH_ELEMENTS[j++]}" SYS_DSID="${XPATH_ELEMENTS[j++]}" @@ -93,11 +93,15 @@ unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <(onedatastore show -x $SYS_DSID| $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ /DATASTORE/TEMPLATE/CPG \ /DATASTORE/TEMPLATE/THIN \ /DATASTORE/TEMPLATE/DEDUP \ /DATASTORE/TEMPLATE/COMPRESSION \ - /DATASTORE/TEMPLATE/NAMING_TYPE \ /DATASTORE/TEMPLATE/QOS_ENABLE \ /DATASTORE/TEMPLATE/QOS_PRIORITY \ /DATASTORE/TEMPLATE/QOS_MAX_IOPS \ @@ -106,11 +110,15 @@ done < <(onedatastore show -x $SYS_DSID| $XPATH \ /DATASTORE/TEMPLATE/QOS_MIN_BW \ /DATASTORE/TEMPLATE/QOS_LATENCY) +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" CPG="${XPATH_ELEMENTS[j++]:-$CPG}" THIN="${XPATH_ELEMENTS[j++]:-$THIN}" DEDUP="${XPATH_ELEMENTS[j++]:-$DEDUP}" COMPRESSION="${XPATH_ELEMENTS[j++]:-$COMPRESSION}" -NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" QOS_PRIORITY="${XPATH_ELEMENTS[j++]:-$QOS_PRIORITY}" QOS_MAX_IOPS="${XPATH_ELEMENTS[j++]:-$QOS_MAX_IOPS}" @@ -122,7 +130,7 @@ QOS_LATENCY="${XPATH_ELEMENTS[j++]:-$QOS_LATENCY}" #------------------------------------------------------------------------------- # Start actions #------------------------------------------------------------------------------- - +# TODO: remote copy support NEW_NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py createVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ -p $PASSWORD -nt $NAMING_TYPE -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION -sn $NAME \ -vi $VMID -id $DISK_ID -c $CPG -sz $SIZE -co "$VM_NAME") @@ -147,7 +155,7 @@ fi if [ "$QOS_ENABLE" == "YES" ]; then log "Create QoS Policy" QOS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py createQosPolicy -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NEW_NAME -vi $VMID -qp $QOS_PRIORITY -qxi $QOS_MAX_IOPS -qmi $QOS_MIN_IOPS \ + -nt $NAMING_TYPE -vi $VMID -qp $QOS_PRIORITY -qxi $QOS_MAX_IOPS -qmi $QOS_MIN_IOPS \ -qxb $QOS_MAX_BW -qmb $QOS_MIN_BW -ql $QOS_LATENCY) if [ $? -ne 0 ]; then diff --git a/tm/3par/context b/tm/3par/context index 43ce0fc2efbe552887eee9d9accda528b1bde063..c5e290a41bb3a46dbca1eeba81b3049e97c8e0ea 100644 --- a/tm/3par/context +++ b/tm/3par/context @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems # +# Copyright 2002-2022, OpenNebula Project, OpenNebula Systems # # # # 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 # @@ -51,6 +51,7 @@ function exit_error DST_PATH=`arg_path $DST` DST_HOST=`arg_host $DST` DST_DIR=`dirname $DST_PATH` +DST_FILE=`basename $DST_PATH` #------------------------------------------------------------------------------- # Create DST path @@ -65,10 +66,14 @@ log "Generating context block device at $DST" VM_ID=`basename $DST_DIR` ISO_DIR="$DS_DIR/.isofiles/$VM_ID" -ISO_FILE="$ISO_DIR/$VM_ID.iso" +ISO_FILE="$VM_ID.iso" +ISO_PATH="$ISO_DIR/$ISO_FILE" + +exec_and_log "rm -rf $ISO_DIR" \ + "Could not delete temp. to make context dev" exec_and_set_error "mkdir -p $ISO_DIR" \ - "Could not create tmp dir to make context dev" + "Could not create temp. dir to make context dev" [ -n "$ERROR" ] && exit_error for f in "${SRC[@]}"; do @@ -93,13 +98,30 @@ for f in "${SRC[@]}"; do [ -n "$ERROR" ] && exit_error done -exec_and_set_error "$MKISOFS -o $ISO_FILE -V CONTEXT -J -R $ISO_DIR" \ - "Error creating iso fs" +# This generates context ISO first into a temporary file and renames to final +# file, to workaround problem when datastores are on FUSE mounted volume +# (e.g,. fuse-overlayfs), cached files metadata are not consistent and +# and tar sparse detection algorithm could identify file as empty. +MKCONTEXT_CMD=$(cat <<EOF + set -e -o pipefail + $MKISOFS -o $ISO_PATH.tmp -V CONTEXT -J -R $ISO_DIR + mv $ISO_PATH.tmp $ISO_PATH +EOF +) + +multiline_exec_and_set_error "$MKCONTEXT_CMD" "Error creating iso fs" [ -n "$ERROR" ] && exit_error -exec_and_set_error "$SCP $ISO_FILE $DST" "Error copying context ISO to $DST" +COPY_CMD=$(cat <<EOF + set -e -o pipefail + $TAR -C $ISO_DIR --transform="flags=r;s|$ISO_FILE|$DST_FILE|" -cSf - $ISO_FILE | \ + $SSH $DST_HOST "$TAR -xSf - -C $DST_DIR" +EOF +) + +multiline_exec_and_set_error "$COPY_CMD" "Error copying context ISO to $DST" [ -n "$ERROR" ] && exit_error rm -rf $ISO_DIR > /dev/null 2>&1 -exit 0 +exit 0 \ No newline at end of file diff --git a/tm/3par/cpds b/tm/3par/cpds index 394eb26d74000b1cad77db868b49313c4f0d7402..d2c47f7b5e5bc9228461a08752023d612a800727 100644 --- a/tm/3par/cpds +++ b/tm/3par/cpds @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -46,6 +46,10 @@ DRIVER_PATH=$(dirname $0) source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf . ${DRIVER_PATH}/../../datastore/3par/scripts_3par.sh +# preserve vars from conf file +CONF_API_ENDPOINT="$API_ENDPOINT" +CONF_IP="$IP" + # -------- Get cpds and datastore arguments from OpenNebula core ------------ SRC=$1 @@ -54,19 +58,28 @@ SNAP_ID=$3 VMID=$4 DSID=$5 +SRC=`fix_dir_slashes $SRC` +SRC_PATH=`arg_path $SRC` +SRC_HOST=`arg_host $SRC` + #------------------------------------------------------------------------------- # Get dest ds information #------------------------------------------------------------------------------- XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $DSID | $XPATH /DATASTORE/TEMPLATE/CPG) +done < <(onedatastore show -x $DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/CPG) -CPG="${XPATH_ELEMENTS[0]:-$CPG}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$CONF_API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$CONF_IP}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" #------------------------------------------------------------------------------- # Get Image information @@ -74,39 +87,76 @@ CPG="${XPATH_ELEMENTS[0]:-$CPG}" DISK_ID=$(basename ${SRC} | cut -d. -f2) -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -IMAGE_ID="${XPATH_ELEMENTS[0]}" -CLONE="${XPATH_ELEMENTS[1]}" -SYS_DSID="${XPATH_ELEMENTS[2]}" +IMAGE_ID="${XPATH_ELEMENTS[j++]}" +CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMAGE_NAME_WWN="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" + +NAME=$(get_vv_name "$DST") +WWN=$(get_vv_wwn "$DST") #------------------------------------------------------------------------------- # Get system ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP) -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" - -NAME=$(get_vv_name "$DST") +SYS_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$CONF_API_ENDPOINT}" +SYS_IP="${XPATH_ELEMENTS[j++]:-$CONF_IP}" log "Copy disk id $DISK_ID attached on VM $VMID to new disk $NAME" -if [ "$CLONE" != "YES" ]; then +# Not clone and not volatile +if [ "$CLONE" == "NO" ] && [ "$DISK_TYPE" == "BLOCK" ]; then DISK_ID=$IMAGE_ID - CLONE=0 fi -python ${DRIVER_PATH}/../../datastore/3par/3par.py copyVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -d $NAME -c $CPG -vc $CLONE \ No newline at end of file +# check IPs of SYSTEM DS and IMAGE DS +if [ "$IP" != "$SYS_IP" ]; then + log "SYSTEM DS is on remote storage system" + + # Not clone and not volatile + if [ "$CLONE" == "NO" ] && [ "$DISK_TYPE" == "BLOCK" ]; then + SRC_WWN=$(get_vv_wwn "$IMAGE_NAME_WWN") + else + # get VM disk WWN + SRC_NAME_WWN=$(get_vm_clone_vv_source "$SYS_API_ENDPOINT" "$SYS_IP" "$VMID" "$DISK_ID") + SRC_WWN=$(get_vv_wwn "$SRC_NAME_WWN") + fi + + # export new vv and clone old one to it + LUN=$(export_vv "$NAME" "$SRC_HOST") + map_and_copy_to_lun "$SRC_HOST" "$SRC_WWN" "$LUN" "$WWN" + + # unexport new vv + unmap_lun "$SRC_HOST" "$WWN" + unexport_vv "$NAME" "$SRC_HOST" +else + COPY=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py copyVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -nt $NAMING_TYPE -id $DISK_ID -si $SNAP_ID -vi $VMID -d $NAME -c $CPG -vc $CLONE) + + if [ $? -ne 0 ]; then + error_message "$COPY" + exit 1 + fi +fi diff --git a/tm/3par/delete b/tm/3par/delete index d67eaf8a7a4c5ada4f086e08218b7b1ca39028e2..57b5a812a3aa256cf4afb45aed7c64c427f81c95 100644 --- a/tm/3par/delete +++ b/tm/3par/delete @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -96,27 +96,22 @@ DISK_ID=$(echo "$DST_PATH" | $AWK -F. '$NF!=$0 {print $NF}') XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ - /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/PERSISTENT \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -NAME_WWN="${XPATH_ELEMENTS[0]}" -PERSISTENT="${XPATH_ELEMENTS[1]}" -CLONE="${XPATH_ELEMENTS[2]}" -DISK_TYPE="${XPATH_ELEMENTS[3]}" -IMAGE_ID="${XPATH_ELEMENTS[4]}" -SYS_DSID="${XPATH_ELEMENTS[5]}" - -# Exit if persistent -[ "$PERSISTENT" == "YES" ] && exit 0 +NAME_WWN="${XPATH_ELEMENTS[j++]}" +CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMAGE_ID="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" # Not persistent and not clone, so this disk can be used by more VMs at the same time if [ "$CLONE" == "NO" ] && [ "$DISK_TYPE" == "BLOCK" ]; then @@ -134,40 +129,36 @@ if [ "$CLONE" == "NO" ] && [ "$DISK_TYPE" == "BLOCK" ]; then fi #------------------------------------------------------------------------------- -# Get image ds information +# Get system ds information #------------------------------------------------------------------------------- unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $DSID | $XPATH \ - /DATASTORE/TEMPLATE/NAMING_TYPE \ - /DATASTORE/TEMPLATE/QOS_ENABLE) - -NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" + XPATH_ELEMENTS[i++]="$element" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ + /DATASTORE/TEMPLATE/QOS_ENABLE) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" # if clone or volatile = non-persistent disk, get right name and wwn if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then - #------------------------------------------------------------------------------- - # Get system ds information - #------------------------------------------------------------------------------- - - unset i j XPATH_ELEMENTS - - while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" - done < <(onedatastore show -x $SYS_DSID | $XPATH \ - /DATASTORE/TEMPLATE/NAMING_TYPE \ - /DATASTORE/TEMPLATE/QOS_ENABLE) - - NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" - QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" # get VM disk WWN - NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) + NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) if [ $? -ne 0 ]; then error_message "$NAME_WWN" @@ -179,8 +170,8 @@ NAME=$(get_vv_name "$NAME_WWN") WWN=$(get_vv_wwn "$NAME_WWN") # Check if DST host is 3PAR host, so compute node -DST_HOST_3PAR=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py hostExists -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -hs $DST_HOST) +DST_HOST_3PAR=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py hostExists -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -hs $DST_HOST) if [ $? -ne 0 ]; then error_message "$DST_HOST_3PAR" @@ -196,6 +187,7 @@ if [ "$DST_HOST_3PAR" == "1" ]; then FLUSH_CMD=$(cat <<EOF set -e $(remove_lun "$WWN") + rm -f "$DST_PATH" EOF ) @@ -204,8 +196,8 @@ EOF ssh_exec_and_log "$DST_HOST" "$FLUSH_CMD" \ "Error flushing out mapping" - python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -n $NAME -hs $DST_HOST + python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ + -p $PASSWORD -n $NAME -hs $DST_HOST if [ $? -ne 0 ]; then error_message "Error unexporting VV" @@ -213,24 +205,34 @@ EOF fi fi -if [ "$QOS_ENABLE" == "YES" ]; then - log "Delete QoS Policy" - QOS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteQosPolicy -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NAME -vi $VMID) +if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $SRC_HOST -rc $REMOTE_COPY - if [ $? -ne 0 ]; then - error_message "$QOS" - exit 1 - fi + if [ $? -ne 0 ]; then + error_message "Error unexporting remote VV" + exit 1 + fi fi -log "Remove disk from VM VV Set" -VVSET=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromVVSet -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NAME -vi $VMID) +if [ "$REMOTE_COPY" == "YES" ]; then + log "Remove disk from Remote Copy group" + RCG=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -n $NAME -vi $VMID) -if [ $? -ne 0 ]; then - error_message "$VVSET" - exit 1 + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi +else + log "Remove disk from VM VV Set" + VVSET=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromVVSet -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -n $NAME -vi $VMID) + + if [ $? -ne 0 ]; then + error_message "$VVSET" + exit 1 + fi fi # Exit if not clone and not volatile @@ -240,5 +242,5 @@ fi # Delete non-persistent image copy #------------------------------------------------------------------------------- -python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -vi $VMID -id $DISK_ID \ No newline at end of file +python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ + -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID \ No newline at end of file diff --git a/tm/3par/failmigrate b/tm/3par/failmigrate index 9142fe5dc4fc5c6ca1d4b8f788c0b3f8b4e06a7c..8eec56d37e2cefbdf8cff294dd0a743934cbf7df 100755 --- a/tm/3par/failmigrate +++ b/tm/3par/failmigrate @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # diff --git a/tm/3par/ln b/tm/3par/ln index 2403a488c61211b6921816cac66013be3c8d8e0d..6fb2905e0347fa4ea113a7e708be462324dc132d 100644 --- a/tm/3par/ln +++ b/tm/3par/ln @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -29,7 +29,7 @@ # - host is the target host to deploy the VM # - remote_system_ds is the path for the system datastore in the host # - vmid is the id of the VM -# - dsid is the target datastore (0 is the system datastore) +# - dsid is the source datastore (0 is the system datastore) # ------------ Set up the environment to source common tools ------------ @@ -77,31 +77,47 @@ unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onevm show -x $VMID | $XPATH /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID) +done < <(onevm show -x $VMID | $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/TYPE \ + /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -SRC_DSID="${XPATH_ELEMENTS[j++]}" +IMAGE_ID="${XPATH_ELEMENTS[j++]}" +CLONE="${XPATH_ELEMENTS[j++]}" +TYPE="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" #------------------------------------------------------------------------------- -# Get dest ds information +# Get image ds information #------------------------------------------------------------------------------- unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) +done < <(onedatastore show -x $DSID | $XPATH \ + /DATASTORE/TEMPLATE/CPG \ + /DATASTORE/TEMPLATE/IP) -NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +IMG_IP="${XPATH_ELEMENTS[j++]:-$IP}" #------------------------------------------------------------------------------- -# Get src ds information +# Get system ds information #------------------------------------------------------------------------------- unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SRC_DSID | $XPATH \ +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ + /DATASTORE/TEMPLATE/SEC_CPG \ /DATASTORE/TEMPLATE/QOS_ENABLE \ /DATASTORE/TEMPLATE/QOS_PRIORITY \ /DATASTORE/TEMPLATE/QOS_MAX_IOPS \ @@ -110,6 +126,12 @@ done < <(onedatastore show -x $SRC_DSID | $XPATH \ /DATASTORE/TEMPLATE/QOS_MIN_BW \ /DATASTORE/TEMPLATE/QOS_LATENCY) +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" QOS_PRIORITY="${XPATH_ELEMENTS[j++]:-$QOS_PRIORITY}" QOS_MAX_IOPS="${XPATH_ELEMENTS[j++]:-$QOS_MAX_IOPS}" @@ -118,24 +140,54 @@ QOS_MAX_BW="${XPATH_ELEMENTS[j++]:-$QOS_MAX_BW}" QOS_MIN_BW="${XPATH_ELEMENTS[j++]:-$QOS_MIN_BW}" QOS_LATENCY="${XPATH_ELEMENTS[j++]:-$QOS_LATENCY}" +#------------------------------------------------------------------------------- +# Check for compatibility +#------------------------------------------------------------------------------- +if [ "$IMG_IP" != "$IP" ]; then + # TODO: add support in ds/clone to clone between storage systems and add hint to the following error_message + error_message "The image $IMAGE_ID is located in different storage system. Can not deploy to this system datastore!" + exit 1 +fi + #------------------------------------------------------------------------------- # Start actions #------------------------------------------------------------------------------- +# Disk is CDROM +if [ "$CLONE" = "NO" ] && [ "$TYPE" == "CDROM" ]; then + # Disable remote copy + REMOTE_COPY="NO" +fi -log "Add disk to VM VV Set" -VVSET=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py addVolumeToVVSet -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NAME -vi $VMID) +if [ "$REMOTE_COPY" == "YES" ]; then + log "Create remote copy group" + RCG=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py addVolumeToRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -n $NAME -vi $VMID \ + -c $CPG -sc $SEC_CPG -rcm $REMOTE_COPY_MODE) -if [ $? -ne 0 ]; then - error_message "$VVSET" - exit 1 + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi + + log "Add info about RC to image template" + image_update $IMAGE_ID "RC=YES RC_SYSTEM_DS_ID=$SYS_DSID" +else + log "Add disk to VM VV Set" + VVSET=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py addVolumeToVVSet -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -n $NAME -vi $VMID) + + if [ $? -ne 0 ]; then + error_message "$VVSET" + exit 1 + fi fi if [ "$QOS_ENABLE" == "YES" ]; then log "Create QoS Policy" - QOS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py createQosPolicy -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NAME -vi $VMID -qp $QOS_PRIORITY -qxi $QOS_MAX_IOPS -qmi $QOS_MIN_IOPS \ - -qxb $QOS_MAX_BW -qmb $QOS_MIN_BW -ql $QOS_LATENCY) + QOS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py createQosPolicy -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -vi $VMID \ + -qp $QOS_PRIORITY -qxi $QOS_MAX_IOPS -qmi $QOS_MIN_IOPS -qxb $QOS_MAX_BW -qmb $QOS_MIN_BW -ql $QOS_LATENCY \ + -rc $REMOTE_COPY) if [ $? -ne 0 ]; then error_message "$QOS" @@ -143,6 +195,27 @@ if [ "$QOS_ENABLE" == "YES" ]; then fi fi +if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + log "Mapping remote $SRC to $DST_HOST" + + LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $DST_HOST -rc $REMOTE_COPY) + + if [ $? -ne 0 ]; then + error_message "$LUN" + exit 1 + fi + + RESCAN_CMD=$(cat <<EOF + set -e + $(rescan_scsi_bus "$LUN") +EOF +) + + ssh_exec_and_log "$DST_HOST" "$RESCAN_CMD" \ + "Error registering remote $SRC to $DST_HOST" +fi + log "Mapping $SRC to $DST_HOST" LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ diff --git a/tm/3par/mkimage b/tm/3par/mkimage index 2e0ad1c0396aa9c39724c19cd864ac836505705f..07170bf01d3d80395235d4330d18fce53737e7a3 100755 --- a/tm/3par/mkimage +++ b/tm/3par/mkimage @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -71,9 +71,12 @@ unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <(onevm show -x $VMID| $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/FS \ /VM/NAME) -VM_NAME="${XPATH_ELEMENTS[j++]}" + +FS="${XPATH_ELEMENTS[j++]}" +VM_NAME="${XPATH_ELEMENTS[j++]//[^A-Za-z0-9\[\]() _~+-]/}" #------------------------------------------------------------------------------- # Get system ds information @@ -84,11 +87,16 @@ unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <(onedatastore show -x $DSID| $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ /DATASTORE/TEMPLATE/CPG \ + /DATASTORE/TEMPLATE/SEC_CPG \ /DATASTORE/TEMPLATE/THIN \ /DATASTORE/TEMPLATE/DEDUP \ /DATASTORE/TEMPLATE/COMPRESSION \ - /DATASTORE/TEMPLATE/NAMING_TYPE \ /DATASTORE/TEMPLATE/QOS_ENABLE \ /DATASTORE/TEMPLATE/QOS_PRIORITY \ /DATASTORE/TEMPLATE/QOS_MAX_IOPS \ @@ -97,11 +105,16 @@ done < <(onedatastore show -x $DSID| $XPATH \ /DATASTORE/TEMPLATE/QOS_MIN_BW \ /DATASTORE/TEMPLATE/QOS_LATENCY) +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" THIN="${XPATH_ELEMENTS[j++]:-$THIN}" DEDUP="${XPATH_ELEMENTS[j++]:-$DEDUP}" COMPRESSION="${XPATH_ELEMENTS[j++]:-$COMPRESSION}" -NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" QOS_PRIORITY="${XPATH_ELEMENTS[j++]:-$QOS_PRIORITY}" QOS_MAX_IOPS="${XPATH_ELEMENTS[j++]:-$QOS_MAX_IOPS}" @@ -113,7 +126,7 @@ QOS_LATENCY="${XPATH_ELEMENTS[j++]:-$QOS_LATENCY}" #------------------------------------------------------------------------------- # Start actions #------------------------------------------------------------------------------- - +# TODO: remote copy support NEW_NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py createVmVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ -p $PASSWORD -nt $NAMING_TYPE -tpvv $THIN -tdvv $DEDUP -compr $COMPRESSION \ -vi $VMID -id $DISK_ID -c $CPG -sz $SIZE -co "$VM_NAME") @@ -138,7 +151,7 @@ fi if [ "$QOS_ENABLE" == "YES" ]; then log "Create QoS Policy" QOS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py createQosPolicy -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NEW_NAME -vi $VMID -qp $QOS_PRIORITY -qxi $QOS_MAX_IOPS -qmi $QOS_MIN_IOPS \ + -nt $NAMING_TYPE -vi $VMID -qp $QOS_PRIORITY -qxi $QOS_MAX_IOPS -qmi $QOS_MIN_IOPS \ -qxb $QOS_MAX_BW -qmb $QOS_MIN_BW -ql $QOS_LATENCY) if [ $? -ne 0 ]; then @@ -157,6 +170,11 @@ if [ $? -ne 0 ]; then exit 1 fi +# Ensure filesystem for raw disks +if [ "$FSTYPE" == "raw" ] && [ -n "$FS" ]; then + FSTYPE=$FS +fi + DISCOVER_CMD=$(cat <<EOF set -e mkdir -p "$DST_DIR" @@ -165,6 +183,8 @@ DISCOVER_CMD=$(cat <<EOF if [ "$FSTYPE" == "swap" ]; then sudo /sbin/mkswap -L swap "\$DEV" + elif [ "$FSTYPE" == "xfs" ] || [ "$FSTYPE" == "ext4" ] || [ "$FSTYPE" == "ext3" ] || [ "$FSTYPE" == "ext2" ]; then + sudo /usr/sbin/mkfs -t "$FSTYPE" "\$DEV" fi EOF ) diff --git a/tm/3par/monitor b/tm/3par/monitor index 7259f8f2dd5b9aa6b91fd3b328376854217cd417..720ec6c69e9de26c6da04b58ad6db2cc083ede50 100644 --- a/tm/3par/monitor +++ b/tm/3par/monitor @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -44,24 +44,53 @@ ID=$2 XPATH="${DRIVER_PATH}/../../datastore/xpath.rb -b $DRV_ACTION" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG \ - /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NAMING_TYPE \ +done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SEC_IP \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/REMOTE_COPY \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/CPG \ /DS_DRIVER_ACTION_DATA/MONITOR_VM_DISKS) -CPG="${XPATH_ELEMENTS[0]:-$CPG}" -NAMING_TYPE="${XPATH_ELEMENTS[1]:-$NAMING_TYPE}" -MONITOR_VM_DISKS="${XPATH_ELEMENTS[2]}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +MONITOR_VM_DISKS="${XPATH_ELEMENTS[j++]}" + +if [ -d "${0%/*}/../../im/kvm-probes.d/vm/monitor" ]; then + LEGACY_MONITORING=0 +else + LEGACY_MONITORING=1 +fi # ------------ Compute datastore usage ------------- -python ${DRIVER_PATH}/../../datastore/3par/3par.py monitorCPG -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -c $CPG -nt $NAMING_TYPE -di $ID -d $MONITOR_VM_DISKS +MONITOR_DATA=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py monitorCPG -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -c $CPG) +MONITOR_STATUS=$? -if [ $? -ne 0 ]; then - error_message "Error monitoring CPG" - exit 1 +if [ $MONITOR_VM_DISKS -eq 1 ]; then + MONITOR_DATA_VMS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py monitorVmDisks -a $API_ENDPOINT -i $IP \ + -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -di $ID -lf $LEGACY_MONITORING) + MONITOR_VMS_STATUS=$? fi + +echo $MONITOR_DATA + +if [ $MONITOR_VM_DISKS -eq 1 ]; then + if [ $LEGACY_MONITORING -eq 0 ]; then + send_to_monitor MONITOR_VM $MONITOR_VMS_STATUS -1 "$MONITOR_DATA_VMS" + else + echo $MONITOR_DATA_VMS + exit $MONITOR_VMS_STATUS + fi +fi + +exit $MONITOR_STATUS diff --git a/tm/3par/mv b/tm/3par/mv index 70a918700dcda3c5e83a16aaca286e996a548d92..a180463950f66ce22e512e6ed919368eca6a2cd8 100644 --- a/tm/3par/mv +++ b/tm/3par/mv @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -20,7 +20,9 @@ # -------------------------------------------------------------------------- # ############################################################################### -# This script is used to move images/directories across system_ds in different hosts. When used for the system datastore the script will received the directory +# This script is used to move images/directories across system_ds in different hosts. +# When used for the system datastore the script will received the directory +# This script is call during undeploy action too ############################################################################### # MV <hostA:system_ds/disk.i|hostB:system_ds/disk.i> vmid dsid @@ -30,6 +32,9 @@ # - vmid is the id of the VM # - dsid is the target datastore (0 is the system datastore) +trap "exit 1" TERM +export TOP_PID=$$ + # ------------ Set up the environment to source common tools ------------ if [ -z "${ONE_LOCATION}" ]; then @@ -45,6 +50,12 @@ DRIVER_PATH=$(dirname $0) source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf . ${DRIVER_PATH}/../../datastore/3par/scripts_3par.sh +# preserve vars from conf file +CONF_API_ENDPOINT="$API_ENDPOINT" +CONF_IP="$IP" +CONF_SEC_API_ENDPOINT="$SEC_API_ENDPOINT" +CONF_SEC_IP="$SEC_IP" + # -------- Get mv and datastore arguments from OpenNebula core ------------ SRC=$1 @@ -74,8 +85,10 @@ fi LCM_STATE=`lcm_state` +# moving system datastore deployment files, not a disk if [ `is_disk $DST_PATH` -eq 0 ]; then # VM is in unknown state, SRC_HOST probably in failed state + # PROLOG_MIGRATE_UNKNOWN PROLOG_MIGRATE_UNKNOWN_FAILURE if [ $LCM_STATE -eq 60 ] || [ $LCM_STATE -eq 61 ]; then log "Not moving files from $SRC_HOST in FT mode" exit 0 @@ -107,131 +120,292 @@ fi DISK_ID=$(echo "$DST_PATH" | $AWK -F. '{print $NF}') -XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" - -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" done < <(onevm show -x $VMID| $XPATH \ + /VM/NAME \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SIZE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/PERSISTENT \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -NAME_WWN="${XPATH_ELEMENTS[0]}" -IMAGE_ID="${XPATH_ELEMENTS[1]}" -CLONE="${XPATH_ELEMENTS[2]}" -PERSISTENT="${XPATH_ELEMENTS[3]}" -SYS_DSID="${XPATH_ELEMENTS[4]}" - -# Disk os clone, so copy was created -if [ "$CLONE" == "YES" ]; then - #------------------------------------------------------------------------------- - # Get system ds information - #------------------------------------------------------------------------------- - - unset i XPATH_ELEMENTS - - while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" - done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - - NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" - - # get VM disk WWN - NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) +VM_NAME="${XPATH_ELEMENTS[j++]//[^A-Za-z0-9\[\]() _~+-]/}" +SIZE="${XPATH_ELEMENTS[j++]}" +NAME_WWN="${XPATH_ELEMENTS[j++]}" +IMAGE_ID="${XPATH_ELEMENTS[j++]}" +CLONE="${XPATH_ELEMENTS[j++]}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" + +# preserve original source +ORG_NAME_WWN="$NAME_WWN" + +PREV_SYS_DSID=$(onevm show -x $VMID | xmllint --xpath 'string(/VM/HISTORY_RECORDS/HISTORY[last()-1]/DS_ID)' -) + +if [ -z "$PREV_SYS_DSID" ]; then + PREV_SYS_DSID=$SYS_DSID +fi + +#------------------------------------------------------------------------------- +# Get system ds information +#------------------------------------------------------------------------------- +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/CPG \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ + /DATASTORE/TEMPLATE/SEC_CPG \ + /DATASTORE/TEMPLATE/THIN \ + /DATASTORE/TEMPLATE/DEDUP \ + /DATASTORE/TEMPLATE/COMPRESSION \ + /DATASTORE/TEMPLATE/QOS_ENABLE \ + /DATASTORE/TEMPLATE/QOS_PRIORITY \ + /DATASTORE/TEMPLATE/QOS_MAX_IOPS \ + /DATASTORE/TEMPLATE/QOS_MIN_IOPS \ + /DATASTORE/TEMPLATE/QOS_MAX_BW \ + /DATASTORE/TEMPLATE/QOS_MIN_BW \ + /DATASTORE/TEMPLATE/QOS_LATENCY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +CPG="${XPATH_ELEMENTS[j++]:-$CPG}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +SEC_CPG="${XPATH_ELEMENTS[j++]:-$SEC_CPG}" +THIN="${XPATH_ELEMENTS[j++]:-$THIN}" +DEDUP="${XPATH_ELEMENTS[j++]:-$DEDUP}" +COMPRESSION="${XPATH_ELEMENTS[j++]:-$COMPRESSION}" +QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" +QOS_PRIORITY="${XPATH_ELEMENTS[j++]:-$QOS_PRIORITY}" +QOS_MAX_IOPS="${XPATH_ELEMENTS[j++]:-$QOS_MAX_IOPS}" +QOS_MIN_IOPS="${XPATH_ELEMENTS[j++]:-$QOS_MIN_IOPS}" +QOS_MAX_BW="${XPATH_ELEMENTS[j++]:-$QOS_MAX_BW}" +QOS_MIN_BW="${XPATH_ELEMENTS[j++]:-$QOS_MIN_BW}" +QOS_LATENCY="${XPATH_ELEMENTS[j++]:-$QOS_LATENCY}" + +#------------------------------------------------------------------------------- +# Get previous system ds information +#------------------------------------------------------------------------------- +unset i j XPATH_ELEMENTS - if [ $? -ne 0 ]; then - error_message "$NAME_WWN" +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onedatastore show -x $PREV_SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +PREV_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$CONF_API_ENDPOINT}" +PREV_IP="${XPATH_ELEMENTS[j++]:-$CONF_IP}" +PREV_SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$CONF_SEC_API_ENDPOINT}" +PREV_SEC_IP="${XPATH_ELEMENTS[j++]:-$CONF_SEC_IP}" +PREV_REMOTE_COPY="${XPATH_ELEMENTS[j++]:-NO}" + +#------------------------------------------------------------------------------- +# Check for compatibility +#------------------------------------------------------------------------------- +if [ "$PREV_REMOTE_COPY" == "NO" ] && [ "$REMOTE_COPY" == "YES" ] && [ "$PREV_IP" != "$IP" ]; then + error_message "You can not deploy to this RC enabled SYSTEM_DS because primary storage is different from actual one. + It will cause overwrite from remote image! + Please select RC enabled SYSTEM_DS with the same primary storage." exit 1 - fi fi +# Overwrite NAME_WWN if disk is clone or volatile +if [ "$CLONE" = "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then + # get VM disk WWN + NAME_WWN=$(get_vm_clone_vv_source "$PREV_API_ENDPOINT" "$PREV_IP" "$VMID" "$DISK_ID") + + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" + PREV_REMOTE_COPY="NO" +fi + +# extract VV Name and Wwn NAME=$(get_vv_name "$NAME_WWN") WWN=$(get_vv_wwn "$NAME_WWN") +# is src/dest host registered on 3par? so is it compute node and not frontend? +IS_SRC_HOST_3PAR=$(host_exists "$SRC_HOST") +IS_DST_HOST_3PAR=$(host_exists "$DST_HOST") + #------------------------------------------------------------------------------- -# Start actions +# Subroutines #------------------------------------------------------------------------------- +function remove_vv { + local API_ENDPOINT="$1" + local IP="$2" + local SEC_API_ENDPOINT="$3" + local SEC_IP="$4" + local NAME="$5" + local WWN="$6" + local IS_MIGRATION="${7:-NO}" + + # flush disk only if src host is not in failed state + if [ $LCM_STATE -ne 60 ] && [ $LCM_STATE -ne 61 ]; then + unmap_lun "$SRC_HOST" "$WWN" + fi -SRC_HOST_3PAR=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py hostExists -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -hs $SRC_HOST) + unexport_vv "$NAME" "$SRC_HOST" -if [ $? -ne 0 ]; then - error_message "$SRC_HOST_3PAR" - exit 1 -fi + if [ "$PREV_REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + unexport_vv "$NAME" "$SRC_HOST" "YES" + fi -DST_HOST_3PAR=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py hostExists -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -hs $DST_HOST) + # VM lcm state is EPILOG_UNDEPLOY + # or migrating between RC and non-RC datastore + # or migrating between storage systems + if [ $LCM_STATE -eq 30 ] || [ "$PREV_REMOTE_COPY" != "$REMOTE_COPY" ] || [ "$IS_MIGRATION" == "YES" ]; then + # perform cleanup + if [ "$PREV_REMOTE_COPY" == "YES" ]; then + remove_vv_from_rcg "$NAME" "$VMID" + else + remove_vv_from_vvset "$NAME" "$VMID" + fi + fi +} + +function add_vv { + local NAME="$1" + local WWN="$2" + local IS_MIGRATION="${3:-NO}" + + # VM lcm state is PROLOG_UNDEPLOY + # or migrating between RC and non-RC datastore + # or migrating between storage systems + if [ $LCM_STATE -eq 31 ] || [ "$PREV_REMOTE_COPY" != "$REMOTE_COPY" ] || [ "$IS_MIGRATION" == "YES" ]; then + if [ "$REMOTE_COPY" == "YES" ]; then + add_vv_to_rcg "$NAME" "$VMID" + + log "Add info about RC to image template" + image_update "$IMAGE_ID" "RC=YES RC_SYSTEM_DS_ID=$SYS_DSID" + else + add_vv_to_vvset "$NAME" "$VMID" + fi + + if [ "$QOS_ENABLE" == "YES" ]; then + create_qos_policy "$VMID" + fi + + if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + LUN=$(export_vv "$NAME" "$DST_HOST" "YES") + rescan_lun "$DST_HOST" "$LUN" + fi + fi -if [ $? -ne 0 ]; then - error_message "$DST_HOST_3PAR" - exit 1 -fi + LUN=$(export_vv "$NAME" "$DST_HOST") + map_lun "$DST_HOST" "$LUN" "$WWN" "$DST_DIR" "$DST_PATH" +} + +#------------------------------------------------------------------------------- +# Start actions +#------------------------------------------------------------------------------- -# Not persistent and not clone, so this disk can be used by more VMs at the same time +# CDROM, so this disk can be used by more VMs at the same time CAN_UNMAP=1 -if [ "$PERSISTENT" != "YES" ] && [ "$CLONE" == "NO" ]; then +if [ "$CLONE" == "NO" ] && [ "$TYPE" == "CDROM" ]; then # check if disk is in use by other VMs - unset i XPATH_ELEMENTS - - while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" - done < <(oneimage show -x $IMAGE_ID| $XPATH /IMAGE/RUNNING_VMS) - - RUNNING_VMS="${XPATH_ELEMENTS[0]}" + RUNNING_VMS=$(get_image_running_vms_count "$IMAGE_ID") # image is used, so can't unmap [ "$RUNNING_VMS" != "1" ] && CAN_UNMAP=0 fi -if [ "$SRC_HOST_3PAR" == "1" ] && [ "$CAN_UNMAP" == "1" ]; then - log "Unmapping $WWN from $SRC_HOST" +# moving persistent disk between storage systems using remote copy, full switchover +# only if VM lcm state not EPILOG_UNDEPLOY +if [ $LCM_STATE -ne 30 ] && [ "$PERSISTENT" == "YES" ] && [ "$IP" != "$PREV_IP" ] && [ "$REMOTE_COPY" == "YES" ] && [ "$PREV_REMOTE_COPY" == "YES" ]; then + # src host is compute node + if [ "$IS_SRC_HOST_3PAR" == "1" ] && [ "$CAN_UNMAP" == "1" ]; then + remove_vv "$PREV_API_ENDPOINT" "$PREV_IP" "$PREV_SEC_API_ENDPOINT" "$PREV_SEC_IP" "$NAME" "$WWN" "YES" + fi - # src host in failed state, can not flush disk before unexport - if [ $LCM_STATE -ne 60 ] && [ $LCM_STATE -ne 61 ]; then - FLUSH_CMD=$(cat <<EOF - set -e - $(remove_lun "$WWN") -EOF -) + # dest host is compute node + if [ "$IS_DST_HOST_3PAR" == "1" ]; then + add_vv "$NAME" "$WWN" "YES" + fi + + exit 0 +fi - ssh_exec_and_log "$SRC_HOST" "$FLUSH_CMD" \ - "Error flushing out mapping" +# if moving to different 3par, but it is not a CDROM and not migrating between RC and non-RC datastore +# only if VM lcm state not EPILOG_UNDEPLOY +if [ $LCM_STATE -ne 30 ] && [ "$IP" != "$PREV_IP" ] && [ "$TYPE" != "CDROM" ] && [ "$PREV_REMOTE_COPY" == "$REMOTE_COPY" ]; then + if [ "$CLONE" != "YES" ] && [ "$DISK_TYPE" != "FILE" ]; then + error_message "Not supported action yet! Can not move persistent disk between storage systems!" + exit 1 fi - python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -n $NAME -hs $SRC_HOST + # and disk is clone or volatile, lets create new vv + SRC_NAME=$(get_vv_name "$ORG_NAME_WWN") + NEW_NAME_WWN=$(create_vm_clone_vv "$SRC_NAME" "$VMID" "$DISK_ID" "$SIZE" "$VM_NAME" "YES") + NEW_NAME=$(get_vv_name "$NEW_NAME_WWN") + NEW_WWN=$(get_vv_wwn "$NEW_NAME_WWN") - if [ $? -ne 0 ]; then - error_message "Error unexporting VV" - exit 1 + # disable qos policy to speed up copy + if [ "$QOS_ENABLE" == "YES" ]; then + disable_qos_policy "$PREV_API_ENDPOINT" "$PREV_IP" "$VMID" fi -fi -if [ "$DST_HOST_3PAR" == "1" ]; then - log "Mapping $NAME_WWN to $DST_HOST" + # export new vv and clone old one to it + LUN=$(export_vv "$NEW_NAME" "$SRC_HOST") + map_and_copy_to_lun "$SRC_HOST" "$WWN" "$LUN" "$NEW_WWN" - LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -n $NAME -hs $DST_HOST) + # unexport new vv only if destination host is different + if [ "$SRC_HOST" != "$DST_HOST" ]; then + unmap_lun "$SRC_HOST" "$NEW_WWN" + unexport_vv "$NEW_NAME" "$SRC_HOST" + fi - if [ $? -ne 0 ]; then - error_message "$LUN" - exit 1 + # src host is compute node, flush, unexport old vv and delete it + if [ "$IS_SRC_HOST_3PAR" == "1" ]; then + remove_vv "$PREV_API_ENDPOINT" "$PREV_IP" "$PREV_SEC_API_ENDPOINT" "$PREV_SEC_IP" "$NAME" "$WWN" "YES" + delete_vm_clone_vv "$PREV_API_ENDPOINT" "$PREV_IP" "$VMID" "$DISK_ID" fi - - DISCOVER_CMD=$(cat <<EOF - set -e - mkdir -p "$DST_DIR" - $(discover_lun "$LUN" "$WWN") - ln -sf "\$DEV" "$DST_PATH" + + # dest host is compute node, export and discover new vv + if [ "$IS_DST_HOST_3PAR" == "1" ]; then + add_vv "$NEW_NAME" "$NEW_WWN" "YES" + + # update disk symlink + CMD=$(cat <<EOF + set -e + DEV="/dev/mapper/3$NEW_WWN" + ln -sf "\$DEV" "$SRC_PATH" EOF ) - - ssh_exec_and_log "$DST_HOST" "$DISCOVER_CMD" \ - "Error registering $NAME_WWN to $DST_HOST" + + ssh_exec_and_log "$SRC_HOST" "$CMD" \ + "Error sym-linking $SRC_PATH on $SRC_HOST" + fi + + exit 0 +fi + +# src host is compute node +if [ "$IS_SRC_HOST_3PAR" == "1" ] && [ "$CAN_UNMAP" == "1" ]; then + remove_vv "$PREV_API_ENDPOINT" "$PREV_IP" "$PREV_SEC_API_ENDPOINT" "$PREV_SEC_IP" "$NAME" "$WWN" +fi + +# dest host is compute node +if [ "$IS_DST_HOST_3PAR" == "1" ]; then + add_vv "$NAME" "$WWN" fi \ No newline at end of file diff --git a/tm/3par/mvds b/tm/3par/mvds index 4216d3212f745149be1149f9eaf9784685a95abc..b337d1c25076b2e7d1c966446d592c5ff0a2b3cb 100644 --- a/tm/3par/mvds +++ b/tm/3par/mvds @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -72,21 +72,41 @@ fi # image already unmapped by undeploy "mv" script [ "$SRC_HOST_3PAR" != "1" ] && exit 0 +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + #------------------------------------------------------------------------------- -# Get target ds information +# Get image information #------------------------------------------------------------------------------- -XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onevm show -x $VMID| $XPATH /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) + +SYS_DSID="${XPATH_ELEMENTS[j++]}" + +#------------------------------------------------------------------------------- +# Get system ds information +#------------------------------------------------------------------------------- unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $DSID | $XPATH \ - /DATASTORE/TEMPLATE/NAMING_TYPE \ +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY \ /DATASTORE/TEMPLATE/QOS_ENABLE) -NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" QOS_ENABLE="${XPATH_ELEMENTS[j++]:-$QOS_ENABLE}" #------------------------------------------------------------------------------- @@ -107,22 +127,32 @@ ssh_exec_and_log "$SRC_HOST" "$FLUSH_CMD" \ python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ -n $NAME -hs $SRC_HOST -if [ "$QOS_ENABLE" == "YES" ]; then - log "Delete QoS Policy" - QOS=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteQosPolicy -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NAME -vi $VMID) +if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $SRC_HOST -rc $REMOTE_COPY if [ $? -ne 0 ]; then - error_message "$QOS" + error_message "Error unexporting remote VV" exit 1 fi fi -log "Remove disk from VM VV Set" -VVSET=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromVVSet -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -n $NAME -vi $VMID) +if [ "$REMOTE_COPY" == "YES" ]; then + log "Remove disk from Remote Copy group" + RCG=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromRCGroup -a $API_ENDPOINT -i $IP \ + -sapi $SEC_API_ENDPOINT -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -n $NAME -vi $VMID) -if [ $? -ne 0 ]; then - error_message "$VVSET" - exit 1 -fi + if [ $? -ne 0 ]; then + error_message "$RCG" + exit 1 + fi +else + log "Remove disk from VM VV Set" + VVSET=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVolumeFromVVSet -a $API_ENDPOINT -i $IP -s $SECURE \ + -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -n $NAME -vi $VMID) + + if [ $? -ne 0 ]; then + error_message "$VVSET" + exit 1 + fi +fi \ No newline at end of file diff --git a/tm/3par/postmigrate b/tm/3par/postmigrate index ad66cf691a4648db55fd265adfcfe54ebc1e59bf..2a20c2b29f8c4f619e20d2450302c321ebbe47a5 100644 --- a/tm/3par/postmigrate +++ b/tm/3par/postmigrate @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -70,37 +70,39 @@ fi # Unmap luns from host #------------------------------------------------------------------------------- -i=1 -while read line; do - DISK_IDS[$i]="$line" - (( i++ )) -done < <(onevm show $VMID --all | $GREP -w "DISK_ID" | $CUT -d\" -f2) +DISK_IDS=$(echo $TEMPLATE_64 | base64 --decode | ${DRIVER_PATH}/../../datastore/xpath.rb --stdin '%m%/VM/TEMPLATE/DISK/DISK_ID') -for j in `seq 1 ${#DISK_IDS[*]}`; do +for k in $DISK_IDS; do XPATH="${DRIVER_PATH}/../../datastore/xpath.rb -b $TEMPLATE_64" - unset i k XPATH_ELEMENTS + unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" - done < <($XPATH /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/SOURCE \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/TM_MAD \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/CLONE \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/READONLY \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/IMAGE_ID \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/DISK_ID \ + done < <($XPATH /VM/TEMPLATE/DISK[DISK_ID=$k]/SOURCE \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/TM_MAD \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/READONLY \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/IMAGE_ID \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DISK_ID \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) - NAME_WWN=${XPATH_ELEMENTS[k++]} - TM_MAD=${XPATH_ELEMENTS[k++]} - CLONE=${XPATH_ELEMENTS[k++]} - READONLY=${XPATH_ELEMENTS[k++]} - IMAGE_ID=${XPATH_ELEMENTS[k++]} - DISK_ID=${XPATH_ELEMENTS[k++]} - SYS_DSID=${XPATH_ELEMENTS[k++]} + NAME_WWN=${XPATH_ELEMENTS[j++]} + TM_MAD=${XPATH_ELEMENTS[j++]} + CLONE=${XPATH_ELEMENTS[j++]} + READONLY=${XPATH_ELEMENTS[j++]} + IMAGE_ID=${XPATH_ELEMENTS[j++]} + DISK_ID=${XPATH_ELEMENTS[j++]} + DISK_TYPE=${XPATH_ELEMENTS[j++]} + IMG_DSID=${XPATH_ELEMENTS[j++]} + SYS_DSID=${XPATH_ELEMENTS[j++]} # Readonly and not clone, so this disk can be used by more VMs at the same time if [ "$CLONE" == "NO" ] && [ "$READONLY" == "YES" ]; then + XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + # check if disk is in use by other VMs unset i XPATH_ELEMENTS @@ -115,19 +117,30 @@ for j in `seq 1 ${#DISK_IDS[*]}`; do fi if [ "$TM_MAD" = "3par" ]; then - if [ "$CLONE" == "YES" ]; then - #------------------------------------------------------------------------------- - # Get system ds information - #------------------------------------------------------------------------------- - - unset i j XPATH_ELEMENTS - - while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" - done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) + #------------------------------------------------------------------------------- + # Get ds information + #------------------------------------------------------------------------------- + XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" - NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" + unset i j XPATH_ELEMENTS + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + + API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" + IP="${XPATH_ELEMENTS[j++]:-$IP}" + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + + # if clone or volatile = non-persistent disk + if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then # get VM disk WWN NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) @@ -136,6 +149,9 @@ for j in `seq 1 ${#DISK_IDS[*]}`; do error_message "$NAME_WWN" exit 1 fi + + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" fi NAME=$(get_vv_name "$NAME_WWN") @@ -159,6 +175,16 @@ EOF error_message "Error unexporting VV" exit 1 fi + + if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + python ${DRIVER_PATH}/../../datastore/3par/3par.py unexportVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $SRC_HOST -rc $REMOTE_COPY + + if [ $? -ne 0 ]; then + error_message "Error unexporting remote VV" + exit 1 + fi + fi fi done diff --git a/tm/3par/premigrate b/tm/3par/premigrate index 15abbd5479b451bcfaca0d5468f5da73ad171c8d..4708eb0863076337e3558f28f7e3c017273e6b4e 100644 --- a/tm/3par/premigrate +++ b/tm/3par/premigrate @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -92,46 +92,57 @@ ssh_exec_and_log "$SRC_HOST" "$TAR_SSH" "Error copying disk directory to target # Discover luns on dst host #-------------------------------------------------------------------------------- -i=1 -while read line -do - DISK_IDS[$i]="$line" - (( i++ )) -done < <(onevm show $VMID --all | $GREP -w "DISK_ID" | $CUT -d\" -f2) +DISK_IDS=$(echo $TEMPLATE_64 | base64 --decode | ${DRIVER_PATH}/../../datastore/xpath.rb --stdin '%m%/VM/TEMPLATE/DISK/DISK_ID') -for j in `seq 1 ${#DISK_IDS[*]}`; do +for k in $DISK_IDS; do XPATH="${DRIVER_PATH}/../../datastore/xpath.rb -b $TEMPLATE_64" - unset i k XPATH_ELEMENTS + unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" - done < <($XPATH /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/SOURCE \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/TM_MAD \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/CLONE \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/DISK_ID \ + done < <($XPATH /VM/TEMPLATE/DISK[DISK_ID=$k]/SOURCE \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/TM_MAD \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DISK_ID \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) - NAME_WWN=${XPATH_ELEMENTS[k++]} - TM_MAD=${XPATH_ELEMENTS[k++]} - CLONE=${XPATH_ELEMENTS[k++]} - DISK_ID=${XPATH_ELEMENTS[k++]} - SYS_DSID=${XPATH_ELEMENTS[k++]} + NAME_WWN=${XPATH_ELEMENTS[j++]} + TM_MAD=${XPATH_ELEMENTS[j++]} + CLONE=${XPATH_ELEMENTS[j++]} + DISK_ID=${XPATH_ELEMENTS[j++]} + DISK_TYPE=${XPATH_ELEMENTS[j++]} + IMG_DSID=${XPATH_ELEMENTS[j++]} + SYS_DSID=${XPATH_ELEMENTS[j++]} if [ "$TM_MAD" = "3par" ]; then - if [ "$CLONE" == "YES" ]; then - #------------------------------------------------------------------------------- - # Get system ds information - #------------------------------------------------------------------------------- - - unset i j XPATH_ELEMENTS - - while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" - done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - - NAMING_TYPE="${XPATH_ELEMENTS[j++]:-$NAMING_TYPE}" + #------------------------------------------------------------------------------- + # Get ds information + #------------------------------------------------------------------------------- + XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + + API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" + IP="${XPATH_ELEMENTS[j++]:-$IP}" + SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" + SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" + REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + + # if clone or volatile = non-persistent disk + if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then # get VM disk WWN NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) @@ -140,11 +151,35 @@ for j in `seq 1 ${#DISK_IDS[*]}`; do error_message "$NAME_WWN" exit 1 fi + + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" fi NAME=$(get_vv_name "$NAME_WWN") WWN=$(get_vv_wwn "$NAME_WWN") + if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + log "Mapping remote $NAME_WWN to $DST_HOST" + + LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $DST_HOST -rc $REMOTE_COPY) + + if [ $? -ne 0 ]; then + error_message "$LUN" + exit 1 + fi + + RESCAN_CMD=$(cat <<EOF + set -e + $(rescan_scsi_bus "$LUN") +EOF +) + + ssh_exec_and_log "$DST_HOST" "$RESCAN_CMD" \ + "Error registering remote $NAME_WWN to $DST_HOST" + fi + log "Mapping $NAME_WWN to $DST_HOST" LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -s $SECURE \ diff --git a/tm/3par/resize b/tm/3par/resize index a350c0a76bdde3f853a7af43dba8f6e5ada278c9..93a4da9bc5bacbf2bff804e1eb7f5979ab910497 100644 --- a/tm/3par/resize +++ b/tm/3par/resize @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -55,7 +55,7 @@ DISK_ID=$(echo "$SRC_PATH" | $AWK -F. '$NF!=$0 {print $NF}') XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" @@ -63,33 +63,43 @@ done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/PERSISTENT \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/READONLY \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -NAME_WWN="${XPATH_ELEMENTS[0]}" -PERSISTENT="${XPATH_ELEMENTS[1]}" -READONLY="${XPATH_ELEMENTS[2]}" -SYS_DSID="${XPATH_ELEMENTS[3]}" +NAME_WWN="${XPATH_ELEMENTS[j++]}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" +READONLY="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" # Exit if readonly [ "$READONLY" == "YES" ] && exit 1 #------------------------------------------------------------------------------- -# Start actions +# Get system ds information #------------------------------------------------------------------------------- -if [ "$PERSISTENT" != "YES" ]; then - #------------------------------------------------------------------------------- - # Get system ds information - #------------------------------------------------------------------------------- - - unset i XPATH_ELEMENTS - - while IFS= read -r -d '' element; do - XPATH_ELEMENTS[i++]="$element" - done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - - NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" +#------------------------------------------------------------------------------- +# Start actions +#------------------------------------------------------------------------------- +if [ "$PERSISTENT" != "YES" ]; then # get VM disk WWN NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) @@ -98,13 +108,16 @@ if [ "$PERSISTENT" != "YES" ]; then error_message "$NAME_WWN" exit 1 fi + + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" fi NAME=$(get_vv_name "$NAME_WWN") WWN=$(get_vv_wwn "$NAME_WWN") CURRENT_SIZE=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVVSize -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -n $NAME -t VSIZE) + -p $PASSWORD -n $NAME -t VSIZE) if [ $? -ne 0 ]; then error_message "$CURRENT_SIZE" @@ -119,14 +132,34 @@ GROW_SIZE=`expr $SIZE - $CURRENT_SIZE` log "Resizing disk $NAME by $GROW_SIZE MB" # resize volume itself -python ${DRIVER_PATH}/../../datastore/3par/3par.py growVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME \ - -gb $GROW_SIZE +python ${DRIVER_PATH}/../../datastore/3par/3par.py growVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ + -n $NAME -gb $GROW_SIZE -nt $NAMING_TYPE -vi $VMID -rc $REMOTE_COPY if [ $? -ne 0 ]; then error_message "Error resizing VV" exit 1 fi +# rescan remote scsi bus +if [ "$REMOTE_COPY" == "YES" ] && [ "$REMOTE_COPY_MODE" == "SYNC" ]; then + LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -n $NAME -hs $SRC_HOST -rc $REMOTE_COPY) + + if [ $? -ne 0 ]; then + error_message "$LUN" + exit 1 + fi + + RESCAN_CMD=$(cat <<EOF + set -e + $(rescan_scsi_bus "$LUN" "force") +EOF +) + + ssh_exec_and_log "$SRC_HOST" "$RESCAN_CMD" \ + "Error rescaning remote for new size" +fi + # rescan scsi bus and resize multipath device LUN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py exportVV -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ -n $NAME -hs $SRC_HOST) diff --git a/tm/3par/snap_create b/tm/3par/snap_create index 32553d9a029c20192fd4afb0a22cb216397e8f6b..069faeab642b9bdfab3d449880246b3956dbdcbe 100644 --- a/tm/3par/snap_create +++ b/tm/3par/snap_create @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -58,12 +58,16 @@ done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID \ /VM/DEPLOY_ID) IMAGE_ID="${XPATH_ELEMENTS[j++]}" DISK_SRC="${XPATH_ELEMENTS[j++]}" CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" SYS_DSID="${XPATH_ELEMENTS[j++]}" DEPLOY_ID="${XPATH_ELEMENTS[j++]}" @@ -76,13 +80,27 @@ fi # Get system ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + +if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" +fi -python ${DRIVER_PATH}/../../datastore/3par/3par.py createSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE \ No newline at end of file +python ${DRIVER_PATH}/../../datastore/3par/3par.py createSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE -rc $REMOTE_COPY \ No newline at end of file diff --git a/tm/3par/snap_create_live b/tm/3par/snap_create_live index 4c04f9d061668adba48c0ab1df2aca60c0c9c0fa..3e31cf11c389382597f3f9972c6cf812c51e1d1a 100644 --- a/tm/3par/snap_create_live +++ b/tm/3par/snap_create_live @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -59,12 +59,16 @@ done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID \ /VM/DEPLOY_ID) IMAGE_ID="${XPATH_ELEMENTS[j++]}" DISK_SRC="${XPATH_ELEMENTS[j++]}" CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" SYS_DSID="${XPATH_ELEMENTS[j++]}" DEPLOY_ID="${XPATH_ELEMENTS[j++]}" @@ -77,24 +81,33 @@ fi # Get system ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + +if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" +fi LIBVIRT_URI="${QEMU_PROTOCOL}://${SRC_HOST}/system" if virsh -c $LIBVIRT_URI domfsfreeze $DEPLOY_ID ; then trap "virsh -c $LIBVIRT_URI domfsthaw $DEPLOY_ID" EXIT TERM INT HUP -elif virsh -c $LIBVIRT_URI suspend $DEPLOY_ID; then - trap "virsh -c $LIBVIRT_URI resume $DEPLOY_ID" EXIT TERM INT HUP -else - error_message "Could not domfsfreeze or suspend domain" - exit 1 fi -python ${DRIVER_PATH}/../../datastore/3par/3par.py createSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE \ No newline at end of file +python ${DRIVER_PATH}/../../datastore/3par/3par.py createSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE -rc $REMOTE_COPY \ No newline at end of file diff --git a/tm/3par/snap_delete b/tm/3par/snap_delete index 20e06dcbe3e124b481fb2c93ffdab04c900a6e81..339c96b3ba0c49bb27d10e1019ef65116cf69e0c 100644 --- a/tm/3par/snap_delete +++ b/tm/3par/snap_delete @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -58,12 +58,16 @@ done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID \ /VM/DEPLOY_ID) IMAGE_ID="${XPATH_ELEMENTS[j++]}" DISK_SRC="${XPATH_ELEMENTS[j++]}" CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" SYS_DSID="${XPATH_ELEMENTS[j++]}" DEPLOY_ID="${XPATH_ELEMENTS[j++]}" @@ -76,13 +80,27 @@ fi # Get system ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + +if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" +fi -python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE \ No newline at end of file +python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE -rc $REMOTE_COPY \ No newline at end of file diff --git a/tm/3par/snap_revert b/tm/3par/snap_revert index bc6b9befb1910239764795e571149f4ae2ff5920..5cbfe0b8b3323ba5a6f6476ca074d7c0803b8de2 100644 --- a/tm/3par/snap_revert +++ b/tm/3par/snap_revert @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright 2014-2016, Laurent Grawet <dev@grawet.be> # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # @@ -67,12 +67,16 @@ done < <(onevm show -x $VMID| $XPATH \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_ID \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/DATASTORE_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID \ /VM/DEPLOY_ID) IMAGE_ID="${XPATH_ELEMENTS[j++]}" NAME_WWN="${XPATH_ELEMENTS[j++]}" CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" SYS_DSID="${XPATH_ELEMENTS[j++]}" DEPLOY_ID="${XPATH_ELEMENTS[j++]}" @@ -85,19 +89,25 @@ fi # Get system ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" - -# We need to unexport volume, revert snapshot and them export volume back - -# If it is not readonly, need to get non-persistent VM disk -# Readonly is for ex. CDROM and no copy is created -if [ "$CLONE" == "YES" ]; then +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + +# if clone or volatile = non-persistent disk +if [ "$CLONE" == "YES" ] || [ "$DISK_TYPE" == "FILE" ]; then # get VM disk WWN NAME_WWN=$(python ${DRIVER_PATH}/../../datastore/3par/3par.py getVmClone -a $API_ENDPOINT -i $IP -s $SECURE \ -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -id $DISK_ID) @@ -106,15 +116,19 @@ if [ "$CLONE" == "YES" ]; then error_message "$NAME_WWN" exit 1 fi + + # Disable remote copy for non-persistent disk + REMOTE_COPY="NO" fi NAME=$(get_vv_name "$NAME_WWN") WWN=$(get_vv_wwn "$NAME_WWN") # revert snapshot -log "Reverting snapshot $SNAP_ID" -python ${DRIVER_PATH}/../../datastore/3par/3par.py revertSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME \ - -p $PASSWORD -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID -vc $CLONE -o $ONLINE +log "Reverting snapshot $SNAP_ID; Online: $ONLINE" +python ${DRIVER_PATH}/../../datastore/3par/3par.py revertSnapshot -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -id $DISK_ID -vi $VMID -si $SNAP_ID \ + -vc $CLONE -o $ONLINE -rc $REMOTE_COPY if [ $? -ne 0 ]; then error_message "Error promoting snapshot back to VV" diff --git a/vmm/checkMultipath.py b/vmm/checkMultipath.py new file mode 100644 index 0000000000000000000000000000000000000000..10e4037883c8b48f6b113e3eb02165ed336917c9 --- /dev/null +++ b/vmm/checkMultipath.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# -------------------------------------------------------------------------- # +# Copyright 2022, FeldHost™ (feldhost.net) # +# # +# 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. # +# -------------------------------------------------------------------------- # + +import sys +import dmmp + +wwid = sys.argv[1] + +mpath = dmmp.mpath_get(wwid) +print("Got mpath: wwid '%s', name '%s'" % (mpath.wwid, mpath.name)) +for pg in mpath.path_groups: + print("\tGot path group: id '%d', priority '%d', status '%d(%s)', " + "selector '%s'" % + (pg.id, pg.priority, pg.status, pg.status_string, pg.selector)) + + target = None + for p in pg.paths: + # check for target wwn, must be same for all paths + if target is not None and target != p.target_wwnn: + print("\t\tPath: blk_name '%s', status '%d(%s)' has different target wwnn '%s'!" % + (p.blk_name, p.status, p.status_string, p.target_wwnn)) + exit(1) + else: + target = p.target_wwnn + print("\t\tGot path: blk_name '%s', status '%d(%s)', target '%s'" % + (p.blk_name, p.status, p.status_string, p.target_wwnn)) diff --git a/vmm/dmmp.py b/vmm/dmmp.py new file mode 100644 index 0000000000000000000000000000000000000000..4c00b4c4f7d076de3eb09c2a70d1caed042442e2 --- /dev/null +++ b/vmm/dmmp.py @@ -0,0 +1,356 @@ +""" +Python API for multipath-tools +""" +# Copyright (C) 2016-2018 Red Hat, Inc. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; If not, see <http://www.gnu.org/licenses/>. +# +# Author: Gris Ge <fge@redhat.com> +# Nir Soffer <nsoffer@redhat.com> +# Kristian Feldsam <feldsam@feldhost.cz> + +import json +import socket +import ctypes +import sys +import struct + +_API_VERSION_MAJOR = 0 + +_IPC_ADDR = "\0/org/kernel/linux/storage/multipathd" + +_IPC_LEN_SIZE = ctypes.sizeof(ctypes.c_ssize_t(0)) + +if sys.version_info[0] < 3: + _CMD_HEAD = struct.Struct("q" if _IPC_LEN_SIZE == 8 else "i") +else: + _CMD_HEAD = struct.Struct("n") + + +class DMMP_path(object): + """ + DMMP_pathgroup is the abstraction of path in multipath-tools. + """ + + def __init__(self, path): + """ + Internal function. For mpaths_get() only. + """ + for key, value in path.items(): + setattr(self, "_%s" % key, value) + + STATUS_UNKNOWN = 0 + STATUS_DOWN = 2 + STATUS_UP = 3 + STATUS_SHAKY = 4 + STATUS_GHOST = 5 + STATUS_PENDING = 6 + STATUS_TIMEOUT = 7 + STATUS_DELAYED = 9 + + _STATUS_CONV = { + "undef": STATUS_UNKNOWN, + "faulty": STATUS_DOWN, + "ready": STATUS_UP, + "shaky": STATUS_SHAKY, + "ghost": STATUS_GHOST, + "i/o pending": STATUS_PENDING, + "i/o timeout": STATUS_TIMEOUT, + "delayed": STATUS_DELAYED, + } + + @property + def blk_name(self): + """ + String. Block name of current path. Examples: "sda", "nvme0n1". + """ + return self._dev + + @property + def target_wwnn(self): + """ + String. Target device WWNN. Examples: "0x2ff70002ac01e918", "0x2ff70002ac01ec48". + """ + return self._target_wwnn + + @property + def status(self): + """ + Integer. Status of current path. Possible values are: + * DMMP_path.STATUS_UNKNOWN + Unknown status. + * DMMP_path.STATUS_DOWN + Path is down and you shouldn't try to send commands to it. + * DMMP_path.STATUS_UP + Path is up and I/O can be sent to it. + * DMMP_path.STATUS_SHAKY + Only emc_clariion checker when path not available for "normal" + operations. + * DMMP_path.STATUS_GHOST + Only hp_sw and rdac checkers. Indicates a "passive/standby" path + on active/passive HP arrays. These paths will return valid answers + to certain SCSI commands (tur, read_capacity, inquiry, start_stop), + but will fail I/O commands. + The path needs an initialization command to be sent to it in order + for I/Os to succeed. + * DMMP_path.STATUS_PENDING + Available for all async checkers when a check IO is in flight. + * DMMP_path.STATUS_TIMEOUT + Only tur checker when command timed out. + * DMMP_path.STATUS_DELAYED + If a path fails after being up for less than delay_watch_checks + checks, when it comes back up again, it will not be marked as up + until it has been up for delay_wait_checks checks. During this + time, it is marked as "delayed". + """ + return self._STATUS_CONV.get(self.status_string, self.STATUS_UNKNOWN) + + @property + def status_string(self): + """ + String. Status of current path. Possible values are: + * "undef" + STATUS_UNKNOWN + * "faulty" + STATUS_DOWN + * "ready" + STATUS_UP + * "shaky" + STATUS_SHAKY + * "ghost" + STATUS_GHOST + * "i/o pending" + STATUS_PENDING + * "i/o timeout" + STATUS_TIMEOUT + * "delayed" + STATUS_DELAYED + """ + return self._chk_st + + def __str__(self): + return "%s|%s" % (self.blk_name, self.status_string) + + +class DMMP_pathgroup(object): + """ + DMMP_pathgroup is the abstraction of path group in multipath-tools. + """ + + def __init__(self, pg): + """ + Internal function. For mpaths_get() only. + """ + self._paths = [] + for key, value in pg.items(): + if key == "paths": + for path in pg["paths"]: + self._paths.append(DMMP_path(path)) + else: + setattr(self, "_%s" % key, value) + + STATUS_UNKNOWN = 0 + STATUS_ENABLED = 1 + STATUS_DISABLED = 2 + STATUS_ACTIVE = 3 + + _STATUS_CONV = { + "undef": STATUS_UNKNOWN, + "enabled": STATUS_ENABLED, + "disabled": STATUS_DISABLED, + "active": STATUS_ACTIVE, + } + + @property + def id(self): + """ + Integer. Group ID of current path group. Could be used for + switching active path group. + """ + return self._group + + @property + def status(self): + """ + Integer. Status of current path group. Possible values are: + * DMMP_pathgroup.STATUS_UNKNOWN + Unknown status + * DMMP_pathgroup.STATUS_ENABLED + Standby to be active + * DMMP_pathgroup.STATUS_DISABLED + Disabled due to all path down + * DMMP_pathgroup.STATUS_ACTIVE + Selected to handle I/O + """ + return self._STATUS_CONV.get(self.status_string, self.STATUS_UNKNOWN) + + @property + def status_string(self): + """ + String. Status of current path group. Possible values are: + * "undef" + STATUS_UNKNOWN + * "enabled" + STATUS_ENABLED + * "disabled" + STATUS_DISABLED + * "active" + STATUS_ACTIVE + """ + return self._dm_st + + @property + def priority(self): + """ + Integer. Priority of current path group. The enabled path group with + highest priority will be next active path group if active path group + down. + """ + return self._pri + + @property + def selector(self): + """ + String. Selector of current path group. Path group selector determines + which path in active path group will be use to next I/O. + """ + return self._selector + + @property + def paths(self): + """ + List of DMMP_path objects. + """ + return self._paths + + def __str__(self): + return "%s|%s|%d" % (self.id, self.status_string, self.priority) + + +class DMMP_mpath(object): + """ + DMMP_mpath is the abstraction of mpath(aka. map) in multipath-tools. + """ + + def __init__(self, mpath): + """ + Internal function. For mpaths_get() only. + """ + self._path_groups = [] + for key, value in mpath.items(): + if key == "path_groups": + for pg in mpath["path_groups"]: + self._path_groups.append(DMMP_pathgroup(pg)) + else: + setattr(self, "_%s" % key, value) + + @property + def wwid(self): + """ + String. WWID of current mpath. + """ + return self._uuid + + @property + def name(self): + """ + String. Name(alias) of current mpath. + """ + return self._name + + @property + def path_groups(self): + """ + List of DMMP_mpath objects. + """ + return self._path_groups + + @property + def paths(self): + """ + List of DMMP_path objects + """ + rc = [] + for pg in self.path_groups: + rc.extend(pg.paths) + return rc + + @property + def kdev_name(self): + """ + The string for DEVNAME used by kernel in uevent. + """ + return self._sysfs + + def __str__(self): + return "'%s'|'%s'" % (self.wwid, self.name) + + +def _ipc_exec(s, cmd): + buff = _CMD_HEAD.pack(len(cmd) + 1) + bytearray(cmd, 'utf-8') + b'\0' + s.sendall(buff) + buff = s.recv(_IPC_LEN_SIZE) + if not buff: + return "" + output_len = _CMD_HEAD.unpack(buff)[0] + output = s.recv(output_len).decode("utf-8") + return output.strip('\x00') + + +def mpaths_get(): + """ + Usage: + Query all multipath devices. + Parameters: + void + Returns: + [DMMP_mpath,] List of DMMP_mpath objects. + """ + rc = [] + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.settimeout(60) + s.connect(_IPC_ADDR) + json_str = _ipc_exec(s, "show maps json") + s.close() + if len(json_str) == 0: + return rc + all_data = json.loads(json_str) + if all_data["major_version"] != _API_VERSION_MAJOR: + raise exception("incorrect version") + + for mpath in all_data["maps"]: + rc.append(DMMP_mpath(mpath)) + return rc + + +def mpath_get(wwid): + """ + Usage: + Query specific multipath device. + Parameters: + wwid (str): wwid of multipath device + Returns: + DMMP_mpath DMMP_mpath object. + """ + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.settimeout(60) + s.connect(_IPC_ADDR) + json_str = _ipc_exec(s, "list multipath {map} json".format(map=wwid)) + s.close() + if len(json_str) == 0: + return rc + all_data = json.loads(json_str) + if all_data["major_version"] != _API_VERSION_MAJOR: + raise exception("incorrect version") + + return DMMP_mpath(all_data["map"]) diff --git a/vmm/kvm/attach_disk b/vmm/kvm/attach_disk index 1eb46d4bc924974ac5e56de5a1301d615e9ab039..ec84df09bfcfb47fe87fe1dd313f5eb57e2d46cd 100644 --- a/vmm/kvm/attach_disk +++ b/vmm/kvm/attach_disk @@ -3,7 +3,7 @@ # -------------------------------------------------------------------------- # # Copyright 2002-2019, OpenNebula Project, OpenNebula Systems # # # -# Portions copyright 2019, FeldHost™ (feldhost.net) # +# Portions copyright 2022, FeldHost™ (feldhost.net) # # Portions copyright 2015-2018, Storpool (storpool.com) # # # # Licensed under the Apache License, Version 2.0 (the "License"); you may # @@ -81,13 +81,27 @@ WRITE_IOPS_SEC_MAX_LENGTH=${WRITE_IOPS_SEC_MAX_LENGTH:-${DEFAULT_ATTACH_WRITE_IO WRITE_IOPS_SEC_MAX=${WRITE_IOPS_SEC_MAX:-${DEFAULT_ATTACH_WRITE_IOPS_SEC_MAX}} # BEGIN 3PAR patch +DRIVER_PATH=$(dirname $0) + +sDev="$(readlink "$SOURCE")" + if [ "$DEVICE" = "disk" ] && [ "$TYPE_XML" = "file" ] && [ "$TYPE_SOURCE" = "file" ]; then - sDev="$(readlink -f "$SOURCE")" if [ "${sDev:0:11}" = "/dev/mapper" ]; then TYPE_XML=block TYPE_SOURCE=dev fi fi + +if [ "${sDev:0:11}" = "/dev/mapper" ]; then + WWID=${sDev:12} + + CHECK=$(python ${DRIVER_PATH}/../checkMultipath.py $WWID) + + if [ $? -ne 0 ]; then + error_message "Could not attach ${SOURCE} (${TARGET}) to ${DOMAIN}, multipath device have multiple targets! $CHECK" + exit 1 + fi +fi # END 3PAR patch # disk XML diff --git a/vmm/kvm/deploy b/vmm/kvm/deploy new file mode 100644 index 0000000000000000000000000000000000000000..40d8ea48d98c51c52f350654bc05fdfeeca78770 --- /dev/null +++ b/vmm/kvm/deploy @@ -0,0 +1,81 @@ +#!/bin/bash + +# -------------------------------------------------------------------------- # +# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems # +# # +# 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. # +#--------------------------------------------------------------------------- # + +source $(dirname $0)/../../etc/vmm/kvm/kvmrc +source $(dirname $0)/../../scripts_common.sh + +DEP_FILE=$1 +DEP_FILE_LOCATION=$(dirname $DEP_FILE) + +mkdir -p $DEP_FILE_LOCATION +cat > $DEP_FILE + +# Compact memory +if [ "x$CLEANUP_MEMORY_ON_START" = "xyes" ]; then + sudo -n sysctl vm.drop_caches=3 vm.compact_memory=1 >/dev/null +fi + +# Create non-volatile memory to store firmware variables if needed +nvram="$(xmllint --xpath '/domain/os/nvram/text()' $DEP_FILE 2>/dev/null)" +if [ -n "${nvram}" ]; then + cp -n "${OVMF_NVRAM}" "${nvram}" +fi + +# BEGIN 3PAR patch +DRIVER_PATH=$(dirname $0) + +DISKS=$(xmllint --xpath "//disk[@type='block']/source/@dev" $DEP_FILE 2>/dev/null | sed 's/dev="//g' | sed 's/"//g') + +for DISK in $DISKS; do + sDev="$(readlink "$DISK")" + if [ "${sDev:0:11}" = "/dev/mapper" ]; then + WWID=${sDev:12} + + CHECK=$(python ${DRIVER_PATH}/../checkMultipath.py $WWID) + + if [ $? -ne 0 ]; then + error_message "Could not create domain, multipath device have multiple targets! $CHECK" + exit -1 + fi + fi +done +# END 3PAR patch + +DATA=`virsh --connect $LIBVIRT_URI create $DEP_FILE` + +if [ "x$?" = "x0" ]; then + + DOMAIN_ID=$(xmllint --xpath '/domain/name/text()' "$DEP_FILE") + UUID=$(virsh --connect $LIBVIRT_URI dominfo $DOMAIN_ID | awk '/UUID:/ {print $2}') + echo $UUID + + # redefine potential snapshots + for SNAPSHOT_MD_XML in $(ls ${DEP_FILE_LOCATION}/snap-*.xml 2>/dev/null); do + + + # replace uuid in the snapshot metadata xml + sed -i "s%<uuid>[[:alnum:]-]*</uuid>%<uuid>$UUID</uuid>%" $SNAPSHOT_MD_XML + + # redefine the snapshot using the xml metadata file + virsh --connect $LIBVIRT_URI snapshot-create $DOMAIN_ID $SNAPSHOT_MD_XML --redefine > /dev/null || true + done + +else + error_message "Could not create domain from $DEP_FILE" + exit -1 +fi \ No newline at end of file diff --git a/vmm/kvm/restore b/vmm/kvm/restore new file mode 100644 index 0000000000000000000000000000000000000000..f854402ad2367f69204b3abab933d765a626804b --- /dev/null +++ b/vmm/kvm/restore @@ -0,0 +1,151 @@ +#!/bin/bash + +# -------------------------------------------------------------------------- # +# Copyright 2002-2022, OpenNebula Project, OpenNebula Systems # +# # +# 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. # +#--------------------------------------------------------------------------- # + +DRIVER_PATH=$(dirname $0) + +source $DRIVER_PATH/../../etc/vmm/kvm/kvmrc +source $DRIVER_PATH/../../scripts_common.sh + +FILE=$1 +HOST=$2 +DEPLOY_ID=$3 + +VM_ID=$4 +DS_ID=$5 + +FILE_XML=${FILE}.xml + +#------------------------------------------------------------------------------- +# Handle DRV_MESSAGE coming from stdin +#------------------------------------------------------------------------------- + +if [ ! -t 0 ]; then + # There is data in stdin, read it + DRV_MESSAGE=$(cat) + + # The data is the driver message. Extracting the System DS TM_MAD + XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + IFS= read -r -d '' TM_MAD < <(echo "$DRV_MESSAGE" | $XPATH /VMM_DRIVER_ACTION_DATA/DATASTORE/TM_MAD) + + # If there is a specific hook for this TM_MAD call it: + RESTORE_TM_FILE="${DRIVER_PATH}/restore.${TM_MAD}" + + if [ -x "$RESTORE_TM_FILE" ]; then + echo "$DRV_MESSAGE" | $RESTORE_TM_FILE $@ + fi +fi + +# Checkpoint file: /var/lib/one//datastores/<DS_ID>/<VM_ID>/checkpoint + +DS_ID=$(basename $(dirname $(dirname $FILE))) +DS_LOCATION=$(dirname $(dirname $(dirname $FILE))) +DS_LOCATION_NON_DOUBLE_SLASH=$(echo "$DS_LOCATION" | sed 's|//|/|g') +VM_DIR=$(dirname $FILE) + +RECALCULATE_CMD=$(cat <<EOF +set -e -o pipefail + +# extract the xml from the checkpoint + +virsh --connect $LIBVIRT_URI save-image-dumpxml $FILE > $FILE_XML + +# Eeplace all occurrences of the DS_LOCATION/<DS_ID>/<VM_ID> with the specific +# DS_ID where the checkpoint is placed. This is done in case there was a +# system DS migration + +sed -i "s%$DS_LOCATION/[0-9]\+/$VM_ID/%$DS_LOCATION/$DS_ID/$VM_ID/%g" $FILE_XML +sed -i "s%$DS_LOCATION_NON_DOUBLE_SLASH/[0-9]\+/$VM_ID/%$DS_LOCATION/$DS_ID/$VM_ID/%g" $FILE_XML +EOF +) + +multiline_exec_and_log "$RECALCULATE_CMD" \ + "Could not recalculate paths in $FILE_XML" + +# Compact memory +if [ "x$CLEANUP_MEMORY_ON_START" = "xyes" ]; then + (sudo -l | grep -q sysctl) && sudo -n sysctl vm.drop_caches=3 vm.compact_memory=1 >/dev/null +fi + +# BEGIN 3PAR patch +DRIVER_PATH=$(dirname $0) + +DISKS=$(xmllint --xpath "//disk[@type='block']/source/@dev" $FILE_XML 2>/dev/null | sed 's/dev="//g' | sed 's/"//g') + +for DISK in $DISKS; do + sDev="$(readlink "$DISK")" + if [ "${sDev:0:11}" = "/dev/mapper" ]; then + WWID=${sDev:12} + + CHECK=$(python ${DRIVER_PATH}/../checkMultipath.py $WWID) + + if [ $? -ne 0 ]; then + error_message "Could not restore domain, multipath device have multiple targets! $CHECK" + exit 1 + fi + fi +done +# END 3PAR patch + +### Restore with retry + +# On RHEL/CentOS 7 with qemu-kvm (1.5), it may happen the QEMU +# segfaults on the very first try to restore from checkpoint. +# We retry 3 times before failing completely. + +function restore_domain { + exec_and_log "virsh --connect $LIBVIRT_URI restore $FILE --xml $FILE_XML" \ + "Could not restore from $FILE" +} + +retry ${VIRSH_RETRIES:-3} restore_domain + +if [ $? -ne 0 ]; then + exit 1 +fi + +# Synchronize VM time on background +if [ "$SYNC_TIME" = "yes" ]; then + ( + for I in $(seq 4 -1 1); do + if virsh --connect $LIBVIRT_URI --readonly dominfo $DEPLOY_ID; then + virsh --connect $LIBVIRT_URI domtime --sync $DEPLOY_ID && exit + [ "$I" -gt 1 ] && sleep 5 + else + exit + fi + done + ) &>/dev/null & +fi + +rm "$FILE" +rm "$FILE_XML" + +# redefine potential snapshots +for SNAPSHOT_MD_XML in $(ls ${VM_DIR}/snap-*.xml 2>/dev/null); do + + # query UUID, but only once + UUID=${UUID:-$(virsh --connect $LIBVIRT_URI dominfo $DEPLOY_ID | grep UUID: | awk '{print $2}')} + + # replace uuid in the snapshot metadata xml + sed -i "s%<uuid>[[:alnum:]-]*</uuid>%<uuid>$UUID</uuid>%" $SNAPSHOT_MD_XML + + # redefine the snapshot using the xml metadata file + virsh --connect $LIBVIRT_URI snapshot-create $DEPLOY_ID $SNAPSHOT_MD_XML --redefine > /dev/null || true +done + +exit 0 \ No newline at end of file diff --git a/vmm/kvm/snapshot_create-3par b/vmm/kvm/snapshot_create-3par index 8cd61c119825d42c0e2e47cdb2e615bc37a95b12..9b7710e045f3d29a8821479f432a668e13bf91cd 100644 --- a/vmm/kvm/snapshot_create-3par +++ b/vmm/kvm/snapshot_create-3par @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -49,25 +49,41 @@ LCM_STATE=`lcm_state` XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onevm show -x $VMID| $XPATH /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) +done < <(onevm show -x $VMID| $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=0]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=0]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=0]/DATASTORE_ID \ + /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -SYS_DSID="${XPATH_ELEMENTS[0]}" +CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" #------------------------------------------------------------------------------- -# Get system ds information +# Get ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" # Live snapshoting only if lcm state is HOTPLUG_SNAPSHOT if [ $LCM_STATE -eq 24 ]; then @@ -83,5 +99,5 @@ if [ $LCM_STATE -eq 24 ]; then fi fi -python ${DRIVER_PATH}/../../datastore/3par/3par.py createVVSetSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -vi $VMID -si $SNAP_ID \ No newline at end of file +python ${DRIVER_PATH}/../../datastore/3par/3par.py createVVSetSnapshot -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -si $SNAP_ID -rc $REMOTE_COPY \ No newline at end of file diff --git a/vmm/kvm/snapshot_delete-3par b/vmm/kvm/snapshot_delete-3par index 17e7d405d218d7f4bf981ff47ad053cdb52c289a..7de94583a479a14611c8e9d05814d2087e5251fa 100644 --- a/vmm/kvm/snapshot_delete-3par +++ b/vmm/kvm/snapshot_delete-3par @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -45,25 +45,41 @@ source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onevm show -x $VMID| $XPATH /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) +done < <(onevm show -x $VMID| $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=0]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=0]/DISK_TYPE \ + /VM/TEMPLATE/DISK[DISK_ID=0]/DATASTORE_ID \ + /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) -SYS_DSID="${XPATH_ELEMENTS[0]}" +CLONE="${XPATH_ELEMENTS[j++]}" +DISK_TYPE="${XPATH_ELEMENTS[j++]}" +IMG_DSID="${XPATH_ELEMENTS[j++]}" +SYS_DSID="${XPATH_ELEMENTS[j++]}" #------------------------------------------------------------------------------- # Get system ds information #------------------------------------------------------------------------------- -unset i XPATH_ELEMENTS +unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" -done < <(onedatastore show -x $SYS_DSID | $XPATH /DATASTORE/TEMPLATE/NAMING_TYPE) - -NAMING_TYPE="${XPATH_ELEMENTS[0]:-$NAMING_TYPE}" - -python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVVSetSnapshot -a $API_ENDPOINT -i $IP -s $SECURE -u $USERNAME -p $PASSWORD \ - -nt $NAMING_TYPE -vi $VMID -si $SNAP_ID \ No newline at end of file +done < <(onedatastore show -x $SYS_DSID | $XPATH \ + /DATASTORE/TEMPLATE/API_ENDPOINT \ + /DATASTORE/TEMPLATE/IP \ + /DATASTORE/TEMPLATE/SEC_API_ENDPOINT \ + /DATASTORE/TEMPLATE/SEC_IP \ + /DATASTORE/TEMPLATE/REMOTE_COPY) + +API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$API_ENDPOINT}" +IP="${XPATH_ELEMENTS[j++]:-$IP}" +SEC_API_ENDPOINT="${XPATH_ELEMENTS[j++]:-$SEC_API_ENDPOINT}" +SEC_IP="${XPATH_ELEMENTS[j++]:-$SEC_IP}" +REMOTE_COPY="${XPATH_ELEMENTS[j++]:-$REMOTE_COPY}" + +python ${DRIVER_PATH}/../../datastore/3par/3par.py deleteVVSetSnapshot -a $API_ENDPOINT -i $IP -sapi $SEC_API_ENDPOINT \ + -sip $SEC_IP -s $SECURE -u $USERNAME -p $PASSWORD -nt $NAMING_TYPE -vi $VMID -si $SNAP_ID -rc $REMOTE_COPY \ No newline at end of file diff --git a/vmm/kvm/snapshot_revert-3par b/vmm/kvm/snapshot_revert-3par index b8731ff87408d2a10b5d19230375a5524e9e4b1d..4534d82823bfeacd001c987728edc8024422a4d1 100644 --- a/vmm/kvm/snapshot_revert-3par +++ b/vmm/kvm/snapshot_revert-3par @@ -1,7 +1,7 @@ #!/bin/bash # -------------------------------------------------------------------------- # -# Copyright 2019, FeldHost™ (feldhost.net) # +# Copyright 2022, FeldHost™ (feldhost.net) # # # # Portions copyright OpenNebula Project (OpenNebula.org), CG12 Labs # # # @@ -43,29 +43,24 @@ source ${DRIVER_PATH}/../../etc/datastore/3par/3par.conf # Get All VM Images and execute TM snap_revert action #------------------------------------------------------------------------------- -i=1 -while read line -do - DISK_IDS[$i]="$line" - (( i++ )) -done < <(onevm show $VMID --all | $GREP -w "DISK_ID" | $CUT -d\" -f2) - REVERT_SCRIPT="${DRIVER_PATH}/../../tm/3par/snap_revert" -for j in `seq 1 ${#DISK_IDS[*]}`; do +DISK_IDS=$(onevm show $VMID -x | ${DRIVER_PATH}/../../datastore/xpath.rb --stdin '%m%/VM/TEMPLATE/DISK/DISK_ID') + +for k in $DISK_IDS; do XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" - unset i k XPATH_ELEMENTS + unset i j XPATH_ELEMENTS while IFS= read -r -d '' element; do XPATH_ELEMENTS[i++]="$element" - done < <(onevm show -x $VMID| $XPATH /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/TM_MAD \ - /VM/TEMPLATE/DISK[DISK_ID=${DISK_IDS[$j]}]/DISK_ID \ + done < <(onevm show -x $VMID| $XPATH /VM/TEMPLATE/DISK[DISK_ID=$k]/TM_MAD \ + /VM/TEMPLATE/DISK[DISK_ID=$k]/DISK_ID \ /VM/HISTORY_RECORDS/HISTORY[last\(\)]/DS_ID) - TM_MAD=${XPATH_ELEMENTS[k++]} - DISK_ID=${XPATH_ELEMENTS[k++]} - SYS_DSID=${XPATH_ELEMENTS[k++]} + TM_MAD=${XPATH_ELEMENTS[j++]} + DISK_ID=${XPATH_ELEMENTS[j++]} + SYS_DSID=${XPATH_ELEMENTS[j++]} if [ "$TM_MAD" = "3par" ]; then $REVERT_SCRIPT "${HOST}:${DATASTORES}/${SYS_DSID}/${VMID}/disk.${DISK_ID}" "s${SNAP_ID}" "${VMID}" 0