mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
* scripts: fix external provision to use correct power state The valid states are poweron and poweroff. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> * strip string while processing powerstate for HyperV Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> * ignore warning that spills over to exten output string Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> --------- Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
305 lines
12 KiB
Python
Executable File
305 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import warnings
|
|
warnings.filterwarnings('ignore')
|
|
|
|
import json
|
|
import sys
|
|
import winrm
|
|
|
|
|
|
def fail(message):
|
|
print(json.dumps({"error": message}))
|
|
sys.exit(1)
|
|
|
|
|
|
def succeed(data):
|
|
print(json.dumps(data))
|
|
sys.exit(0)
|
|
|
|
|
|
class HyperVManager:
|
|
def __init__(self, config_path):
|
|
self.config_path = config_path
|
|
self.data = self.parse_json()
|
|
self.session = self.init_winrm_session()
|
|
|
|
def parse_json(self):
|
|
try:
|
|
with open(self.config_path, 'r') as f:
|
|
json_data = json.load(f)
|
|
|
|
external_host_details = json_data["externaldetails"].get("host", [])
|
|
data = {
|
|
"url": external_host_details["url"],
|
|
"username": external_host_details["username"],
|
|
"password": external_host_details["password"],
|
|
"network_switch": external_host_details["network_switch"],
|
|
"vhd_path": external_host_details["vhd_path"],
|
|
"vm_path": external_host_details["vm_path"],
|
|
"cert_validation": "validate" if external_host_details.get("verify_tls_certificate", "true").lower() == "true" else "ignore"
|
|
}
|
|
|
|
external_vm_details = json_data["externaldetails"].get("virtualmachine", [])
|
|
if external_vm_details:
|
|
data["template_type"] = external_vm_details["template_type"]
|
|
data["generation"] = external_vm_details.get("generation", 1)
|
|
data["template_path"] = external_vm_details.get("template_path", "")
|
|
data["iso_path"] = external_vm_details.get("iso_path", "")
|
|
data["vhd_size_gb"] = external_vm_details.get("vhd_size_gb", "")
|
|
|
|
data["cpus"] = json_data["cloudstack.vm.details"]["cpus"]
|
|
data["memory"] = json_data["cloudstack.vm.details"]["minRam"]
|
|
data["vmname"] = json_data["cloudstack.vm.details"]["name"]
|
|
|
|
nics = json_data["cloudstack.vm.details"].get("nics", [])
|
|
data["nics"] = []
|
|
for nic in nics:
|
|
data["nics"].append({
|
|
"mac": nic["mac"],
|
|
"vlan": nic["broadcastUri"].replace("vlan://", "")
|
|
})
|
|
|
|
parameters = json_data.get("parameters", [])
|
|
if parameters:
|
|
data["snapshot_name"] = parameters.get("snapshot_name", "")
|
|
|
|
return data
|
|
|
|
except KeyError as e:
|
|
fail(f"Missing required field in JSON: {str(e)}")
|
|
except Exception as e:
|
|
fail(f"Error parsing JSON: {str(e)}")
|
|
|
|
def init_winrm_session(self):
|
|
return winrm.Session(
|
|
f"https://{self.data['url']}:5986/wsman",
|
|
auth=(self.data["username"], self.data["password"]),
|
|
transport='ntlm',
|
|
server_cert_validation=self.data["cert_validation"]
|
|
)
|
|
|
|
def run_ps_int(self, command):
|
|
r = self.session.run_ps(command)
|
|
if r.status_code != 0:
|
|
raise Exception(r.std_err.decode())
|
|
return r.std_out.decode()
|
|
|
|
def run_ps(self, command):
|
|
try:
|
|
output = self.run_ps_int(command)
|
|
return output
|
|
except Exception as e:
|
|
fail(str(e))
|
|
|
|
def vm_not_present(self, exception):
|
|
vm_not_present_str = f'Hyper-V was unable to find a virtual machine with name "{self.data["vmname"]}"'
|
|
return vm_not_present_str in str(exception)
|
|
|
|
def create(self):
|
|
vm_name = self.data["vmname"]
|
|
cpus = self.data["cpus"]
|
|
memory = self.data["memory"]
|
|
memory_mb = int(memory) / 1024 / 1024
|
|
template_path = self.data["template_path"]
|
|
vhd_path = self.data["vhd_path"] + "\\" + vm_name + ".vhdx"
|
|
vhd_size_gb = self.data["vhd_size_gb"]
|
|
generation = self.data["generation"]
|
|
iso_path = self.data["iso_path"]
|
|
network_switch = self.data["network_switch"]
|
|
vm_path = self.data["vm_path"]
|
|
template_type = self.data.get("template_type", "template")
|
|
|
|
vhd_created = False
|
|
vm_created = False
|
|
vm_started = False
|
|
try:
|
|
command = (
|
|
f'New-VM -Name "{vm_name}" -MemoryStartupBytes {memory_mb}MB '
|
|
f'-Generation {generation} -Path "{vm_path}" '
|
|
)
|
|
if template_type == "iso":
|
|
if (iso_path == ""):
|
|
fail("Missing required field in JSON: iso_path")
|
|
if (vhd_size_gb == ""):
|
|
fail("Missing required field in JSON: vhd_size_gb")
|
|
command += (
|
|
f'-NewVHDPath "{vhd_path}" -NewVHDSizeBytes {vhd_size_gb}GB; '
|
|
f'Add-VMDvdDrive -VMName "{vm_name}" -Path "{iso_path}"; '
|
|
)
|
|
else:
|
|
if (template_path == ""):
|
|
fail("Missing required field in JSON: template_path")
|
|
self.run_ps_int(f'Copy-Item "{template_path}" "{vhd_path}"')
|
|
vhd_created = True
|
|
command += f'-VHDPath "{vhd_path}"; '
|
|
|
|
self.run_ps_int(command)
|
|
vm_created = True
|
|
|
|
command = f'Remove-VMNetworkAdapter -VMName "{vm_name}" -Name "Network Adapter" -ErrorAction SilentlyContinue; '
|
|
self.run_ps_int(command)
|
|
|
|
command = f'Set-VMProcessor -VMName "{vm_name}" -Count "{cpus}"; '
|
|
if (generation == 2):
|
|
command += f'Set-VMFirmware -VMName "{vm_name}" -EnableSecureBoot Off; '
|
|
|
|
self.run_ps_int(command)
|
|
|
|
for idx, nic in enumerate(self.data["nics"]):
|
|
adapter_name = f"NIC{idx+1}"
|
|
self.run_ps_int(f'Add-VMNetworkAdapter -VMName "{vm_name}" -SwitchName "{network_switch}" -Name "{adapter_name}"')
|
|
self.run_ps_int(f'Set-VMNetworkAdapter -VMName "{vm_name}" -Name "{adapter_name}" -StaticMacAddress "{nic["mac"]}"')
|
|
self.run_ps_int(f'Set-VMNetworkAdapterVlan -VMName "{vm_name}" -VMNetworkAdapterName "{adapter_name}" -Access -VlanId "{nic["vlan"]}"')
|
|
|
|
self.run_ps_int(f'Start-VM -Name "{vm_name}"')
|
|
vm_started = True
|
|
|
|
succeed({"status": "success", "message": "Instance created"})
|
|
|
|
except Exception as e:
|
|
if vm_started:
|
|
self.run_ps_int(f'Stop-VM -Name "{vm_name}" -Force -TurnOff')
|
|
if vm_created:
|
|
self.run_ps_int(f'Remove-VM -Name "{vm_name}" -Force')
|
|
if vhd_created:
|
|
self.run_ps_int(f'Remove-Item -Path "{vhd_path}" -Force')
|
|
fail(str(e))
|
|
|
|
def start(self):
|
|
self.run_ps(f'Start-VM -Name "{self.data["vmname"]}"')
|
|
succeed({"status": "success", "message": "Instance started"})
|
|
|
|
def stop(self):
|
|
try:
|
|
self.run_ps_int(f'Stop-VM -Name "{self.data["vmname"]}" -Force')
|
|
except Exception as e:
|
|
if self.vm_not_present(e):
|
|
succeed({"status": "success", "message": "Instance stopped"})
|
|
else:
|
|
fail(str(e))
|
|
succeed({"status": "success", "message": "Instance stopped"})
|
|
|
|
def reboot(self):
|
|
self.run_ps(f'Restart-VM -Name "{self.data["vmname"]}" -Force')
|
|
succeed({"status": "success", "message": "Instance rebooted"})
|
|
|
|
def status(self):
|
|
command = f'(Get-VM -Name "{self.data["vmname"]}").State'
|
|
state = self.run_ps(command)
|
|
power_state = "unknown"
|
|
if state.strip().lower() == "running":
|
|
power_state = "poweron"
|
|
elif state.strip().lower() == "off":
|
|
power_state = "poweroff"
|
|
succeed({"status": "success", "power_state": power_state})
|
|
|
|
def delete(self):
|
|
try:
|
|
self.run_ps_int(f'Remove-VM -Name "{self.data["vmname"]}" -Force')
|
|
except Exception as e:
|
|
if self.vm_not_present(e):
|
|
succeed({"status": "success", "message": "Instance deleted"})
|
|
else:
|
|
fail(str(e))
|
|
succeed({"status": "success", "message": "Instance deleted"})
|
|
|
|
def suspend(self):
|
|
self.run_ps(f'Suspend-VM -Name "{self.data["vmname"]}"')
|
|
succeed({"status": "success", "message": "Instance suspended"})
|
|
|
|
def resume(self):
|
|
self.run_ps(f'Resume-VM -Name "{self.data["vmname"]}"')
|
|
succeed({"status": "success", "message": "Instance resumed"})
|
|
|
|
def create_snapshot(self):
|
|
snapshot_name = self.data["snapshot_name"]
|
|
if snapshot_name == "":
|
|
fail("Missing required field in JSON: snapshot_name")
|
|
command = f'Checkpoint-VM -VMName "{self.data["vmname"]}" -SnapshotName "{snapshot_name}"'
|
|
self.run_ps(command)
|
|
succeed({"status": "success", "message": f"Snapshot '{snapshot_name}' created"})
|
|
|
|
def list_snapshots(self):
|
|
command = (
|
|
f'Get-VMSnapshot -VMName "{self.data["vmname"]}" '
|
|
'| Select-Object Name, @{Name="CreationTime";Expression={$_.CreationTime.ToString("s")}} '
|
|
'| ConvertTo-Json'
|
|
)
|
|
snapshots = json.loads(self.run_ps(command))
|
|
succeed({"status": "success", "printmessage": "true", "message": snapshots})
|
|
|
|
def restore_snapshot(self):
|
|
snapshot_name = self.data["snapshot_name"]
|
|
if snapshot_name == "":
|
|
fail("Missing required field in JSON: snapshot_name")
|
|
command = f'Restore-VMSnapshot -VMName "{self.data["vmname"]}" -Name "{snapshot_name}" -Confirm:$false'
|
|
self.run_ps(command)
|
|
succeed({"status": "success", "message": f"Snapshot '{snapshot_name}' restored"})
|
|
|
|
def delete_snapshot(self):
|
|
snapshot_name = self.data["snapshot_name"]
|
|
if snapshot_name == "":
|
|
fail("Missing required field in JSON: snapshot_name")
|
|
command = f'Remove-VMSnapshot -VMName "{self.data["vmname"]}" -Name "{snapshot_name}" -Confirm:$false'
|
|
self.run_ps(command)
|
|
succeed({"status": "success", "message": f"Snapshot '{snapshot_name}' deleted"})
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 3:
|
|
fail("Usage: script.py <operation> '<json-file-path>'")
|
|
|
|
operation = sys.argv[1].lower()
|
|
json_file_path = sys.argv[2]
|
|
|
|
try:
|
|
manager = HyperVManager(json_file_path)
|
|
except FileNotFoundError:
|
|
fail(f"JSON file not found: {json_file_path}")
|
|
except json.JSONDecodeError:
|
|
fail("Invalid JSON in file")
|
|
|
|
operations = {
|
|
"create": manager.create,
|
|
"start": manager.start,
|
|
"stop": manager.stop,
|
|
"reboot": manager.reboot,
|
|
"delete": manager.delete,
|
|
"status": manager.status,
|
|
"suspend": manager.suspend,
|
|
"resume": manager.resume,
|
|
"listsnapshots": manager.list_snapshots,
|
|
"createsnapshot": manager.create_snapshot,
|
|
"restoresnapshot": manager.restore_snapshot,
|
|
"deletesnapshot": manager.delete_snapshot
|
|
}
|
|
|
|
if operation not in operations:
|
|
fail("Invalid action")
|
|
|
|
try:
|
|
operations[operation]()
|
|
except Exception as e:
|
|
fail(str(e))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|