diff options
author | Ethan Lynn <ethanlynnl@vmware.com> | 2019-03-25 16:31:59 +0800 |
---|---|---|
committer | Ethan Lynn <ethanlynnl@vmware.com> | 2019-04-04 18:54:53 +0800 |
commit | 7a02c59adf4fdf4fe79ad34c248679850311a784 (patch) | |
tree | 90f31ee6c261629a2c8ff4dafac6cd579ecf0093 /vio/vio/vsphere/vc.py | |
parent | 04ad05f0302fa1e059b3c3ab3470759dc333e33e (diff) |
Add more functions to vsphere plugin
Add esxi ability to exec shell command.
Add ovf upload function.
Add vmdk validation function.
Change-Id: I41e658e8d35f9ec2ae8c9238954b705997fa1b3b
Issue-ID: MULTICLOUD-488
Signed-off-by: Ethan Lynn <ethanlynnl@vmware.com>
Diffstat (limited to 'vio/vio/vsphere/vc.py')
-rw-r--r-- | vio/vio/vsphere/vc.py | 312 |
1 files changed, 284 insertions, 28 deletions
diff --git a/vio/vio/vsphere/vc.py b/vio/vio/vsphere/vc.py index 7cc6859..deb09a0 100644 --- a/vio/vio/vsphere/vc.py +++ b/vio/vio/vsphere/vc.py @@ -1,12 +1,96 @@ -from vmtest import utils +# Copyright (c) 2019 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +from vio.vsphere import utils +from vio.vsphere import ovf from pyVmomi import vim -from pyVmomi.vim.vm.device import VirtualEthernetCard +# from pyVmomi.vim.vm.device import VirtualEthernetCard +from pyVim import connect +import paramiko + +import os +# import yaml +import requests + + +def new_vc(): + host = os.getenv("VSPHERE_HOST") + username = os.getenv("VSPHERE_USER") + password = os.getenv("VSPHERE_PASS") + nvc = VCenter(host, username, password) + return nvc + + +def new_esxi(): + host = os.getenv("ESXI_HOST") + username = os.getenv("ESXI_USER") + password = os.getenv("ESXI_PASS") + nesxi = Esxi(host, username, password) + return nesxi + + +class Esxi(object): + def __init__(self, host=None, username=None, password=None): + if not (host and username and password): + raise Exception("host or username or password not specified") + self.host = host + self.username = username + self.password = password + self.ssh = self.get_ssh_session() + + def get_ssh_session(self): + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(self.host, username=self.username, password=self.password) + return ssh + + def exec_cmd(self, cmd): + ssh_stdin, ssh_stdout, ssh_stderr = self.ssh.exec_command(cmd) + # import ipdb; ipdb.set_trace() + return { + "stdin": ssh_stdin.read() if ssh_stdin.readable() else "", + "stdout": ssh_stdout.read() if ssh_stdout.readable() else "", + "stderr": ssh_stderr.read() if ssh_stderr.readable() else "", + } + + def flat_vmdk(self, datastore, vmdk): + target_vmdk = vmdk.rstrip(".vmdk") + "-new.vmdk" + print("Extending %s to %s" % (vmdk, target_vmdk)) + vmdk_path = "/vmfs/volumes/%s/%s" % (datastore, vmdk) + target_path = "/vmfs/volumes/%s/%s" % (datastore, target_vmdk) + cmd = "vmkfstools -i %s %s" % (vmdk_path, target_path) + output = self.exec_cmd(cmd) + if output["stderr"]: + raise Exception(output["stderr"]) + output["target_vmdk"] = target_vmdk + return output class VCenter(object): - def __init__(self): - self.vcontent = utils.GetClient() + def __init__(self, host=None, username=None, password=None): + if not (host and username and password): + raise Exception("host or username or password not specified") + self.host = host + self.username = username + self.password = password + self.datacenter = "" + self.cluster = "" + self.datastore = "" + self.insecure = True + self.service_instance = connect.SmartConnectNoSSL( + host=self.host, user=self.username, pwd=self.password) + self.vcontent = self.service_instance.RetrieveContent() + # self.GetClient() def clone_vm(self, src, dst, power_on=False, wait=True): pass @@ -22,26 +106,186 @@ class VCenter(object): # print("there was an error") # task_done = True + def find_datastore(self, ds_name): + datacenters_object_view = \ + self.vcontent.viewManager.CreateContainerView( + self.vcontent.rootFolder, + [vim.Datacenter], + True) + datacenter = None + datastore = None + for dc in datacenters_object_view.view: + datastores_object_view = \ + self.vcontent.viewManager.CreateContainerView( + dc, + [vim.Datastore], + True) + for ds in datastores_object_view.view: + if ds.info.name == ds_name: + datacenter = dc + datastore = ds + return datacenter, datastore + return datacenter, datastore + + def upload_file(self, filepath, datastore, folder="onap-test"): + if not os.path.exists(filepath): + raise Exception("%s not exists" % filepath) + print("Getting datastore %s" % datastore) + dc, ds = self.find_datastore(datastore) + files = [filepath, filepath.rstrip(".vmdk")+"-flat.vmdk"] + upload_count = 0 + for fp in files: + if not os.path.exists(fp): + continue + upload_count += 1 + file_name = fp.split("/")[-1] + remote_file = "/" + folder + "/" + file_name + resource = "/folder" + remote_file + params = {"dsName": ds.info.name, + "dcPath": dc.name} + http_url = "https://" + self.host + ":443" + resource + # print(http_url) + # si, vconetnt = self.GetClient() + # get cookies + client_cookie = self.service_instance._stub.cookie + cookie_name = client_cookie.split("=", 1)[0] + cookie_value = client_cookie.split("=", 1)[1].split(";", 1)[0] + cookie_path = client_cookie.split("=", 1)[1].split( + ";", 1)[1].split(";", 1)[0].lstrip() + cookie_text = " " + cookie_value + "; $" + cookie_path + # Make a cookie + cookie = dict() + cookie[cookie_name] = cookie_text + + # Get the request headers set up + headers = {'Content-Type': 'application/octet-stream'} + + with open(fp, "rb") as f: + # Connect and upload the file + print("Uploading file %s" % filepath) + resp = requests.put(http_url, + params=params, + data=f, + # files={"file": f}, + headers=headers, + cookies=cookie, + verify=False) + # import ipdb; ipdb.set_trace() + if resp.status_code not in [200, 201]: + raise Exception("failed to upload %s to %s: %s" % ( + filepath, datastore, resp.content)) + print(resp) + print("upload success") + return upload_count -class VM(VCenter): - def __init__(self, name): - super(VM, self).__init__() - # self.vcontent = GetClient() - vm = utils.get_obj(self.vcontent, [vim.VirtualMachine], name) + def deploy_ovf(self, vmdk_path, ovf_path=None, datacenter=None, + cluster=None, datastore=None): + if not datacenter and not self.datacenter: + raise Exception("not set datacenter") + if not cluster and not self.cluster: + raise Exception("not set cluster") + if not datastore and not self.datastore: + raise Exception("not set datastore") + # if not ovf_path: + # raise Exception("not set ovf_path") + ovf.deploy_ovf(self.service_instance, vmdk_path, ovf_path, + datacenter, cluster, datastore) + print("Deploy success.") + + def deploy_ova(self, ova_path, datacenter=None, cluster=None, + datastore=None): + pass + + def validate_image(self, vmdk_path, vm_name): + # import ipdb; ipdb.set_trace() + print("finding vm %s" % vm_name) + vmdk_name = vmdk_path.split("/")[-1] + vm = self.find_vm(vm_name) + dc = vm.datacenter + cluster = vm.cluster + ds = vm.status()['datastores'][0] + # vmdk_name = filepath.split("/")[-1] + print("uploading vmdk file %s" % vmdk_name) + ovf.deploy_ovf(self.service_instance, vmdk_path, ovf_path=None, + datacenter=dc.name, cluster=cluster.name, + datastore=ds.name) + tmp_vm = self.find_vm("vmtest-template") + print("attaching disk to vm %s" % vm_name) + # dsfilepath = "[%s] %s/%s" % ( + # ds.name, "vmtest-template", "vmdisk1.vmdk") + dsfilepath = tmp_vm.disks()[0].backing.fileName + print("dsfilepath=%s" % dsfilepath) + vm.add_disk(filepath=dsfilepath) + print("power on vm %s" % vm_name) + ret = vm.power_on() + if ret is not None: + raise Exception("error to poweron vm: %s", ret) + print("power off vm %s" % vm_name) + vm.power_off() + print("Cleaning") + vm.remove_disk("Hard disk 2", retain_file=True) + tmp_vm.delete() + + def find_ds(self, ds_name): + ds = utils.get_obj(self.vcontent, [vim.Datastore], ds_name) + return ds + + def find_dc(self, dc_name): + dc = utils.get_obj(self.vcontent, [vim.Datacenter], dc_name) + return dc + + # def find_cluster(self, cluster): + # cluster = utils.get_obj(self.vcontent, [vim.C], name) + + def find_vm(self, vm_name): + return VM(self, vm_name) + + +class VM(object): + def __init__(self, vc, name): + self.vc = vc + vm = utils.get_obj(self.vc.vcontent, [vim.VirtualMachine], name) if vm is None: raise Exception("VM %s not found" % name) self.vm = vm self.name = name + @property + def datacenter(self): + res = self.vm.resourcePool + while True: + if res is None: + break + if str(res).startswith("'vim.Datacenter"): + return res + res = res.parent + return None + + @property + def cluster(self): + res = self.vm.resourcePool + while True: + if res is None: + break + if str(res).startswith("'vim.ClusterComputeResource"): + return res + res = res.parent + return None + def status(self): - return { + ret = { "name": self.vm.name, - "resource_pool": self.vm.resourcePool.config.entity, + "resource_pool": self.vm.resourcePool, + "cluster": self.cluster, + "datacenter": self.datacenter, "datastores": [ds for ds in self.vm.datastore], - "networks": [net for net in self.vm.network], - "snapshots": [ss for ss in self.vm.snapshots], "power_state": self.vm.runtime.powerState, } + if self.vm.network: + ret["networks"] = [net for net in self.vm.network] + if self.vm.snapshot: + ret["snapshots"] = [ss for ss in self.vm.snapshot] + return ret @property def power_state(self): @@ -53,7 +297,7 @@ class VM(VCenter): # print("power_on task:", task) # result = wait_for_task(task) # print("power_on result:", result) - result = self.wait_for_task(self.vm.PowerOn()) + result = self.vc.wait_for_task(self.vm.PowerOn()) return result def power_off(self): @@ -62,12 +306,12 @@ class VM(VCenter): # print("power_off task:", task) # result = wait_for_task(task) # print("power_off result:", result) - result = self.wait_for_task(self.vm.PowerOff()) + result = self.vc.wait_for_task(self.vm.PowerOff()) return result def delete(self): self.power_off() - result = self.wait_for_task(self.vm.Destroy()) + result = self.vc.wait_for_task(self.vm.Destroy()) return result def clone(self, dst, wait=True): @@ -79,9 +323,9 @@ class VM(VCenter): print("clone task:", task) if wait: print("wait for task:", task) - result = self.wait_for_task(task) + result = self.vc.wait_for_task(task) print("task result:", result) - return VM(result.name) + return VM(self.vc, result.name) return task def add_nic(self, network_name, nic_type="vmxnet3", mac=None): @@ -102,7 +346,8 @@ class VM(VCenter): else: raise Exception("not supported nic type %s" % nic_type) - network = utils.get_obj(self.vcontent, [vim.Network], network_name) + network = utils.get_obj(self.vc.vcontent, [vim.Network], network_name) + VirtualEthernetCard = vim.vm.device.VirtualEthernetCard if isinstance(network, vim.OpaqueNetwork): nic_spec.device.backing = \ VirtualEthernetCard.OpaqueNetworkBackingInfo() @@ -140,7 +385,7 @@ class VM(VCenter): spec.deviceChange = [nic_spec] task = self.vm.ReconfigVM_Task(spec=spec) - result = self.wait_for_task(task) + result = self.vc.wait_for_task(task) # result == None return result @@ -158,11 +403,12 @@ class VM(VCenter): virtual_nic_spec.device = dev spec.deviceChange.append(virtual_nic_spec) task = self.vm.ReconfigVM_Task(spec=spec) - result = self.wait_for_task(task) + result = self.vc.wait_for_task(task) # result == None return result - def add_disk(self, disk_size, disk_type="thin", wait=True): + def add_disk(self, disk_size=0, disk_type="thin", filepath=None, + wait=True): spec = vim.vm.ConfigSpec() unit_number = 0 for dev in self.vm.config.hardware.device: @@ -174,29 +420,39 @@ class VM(VCenter): if isinstance(dev, vim.vm.device.VirtualSCSIController): controller = dev dev_changes = [] - new_disk_kb = int(disk_size) * 1024 * 1024 + if disk_size <= 0 and not filepath: + raise Exception("Neither disk_size nor filepath specified") disk_spec = vim.vm.device.VirtualDeviceSpec() - disk_spec.fileOperation = "create" + if not filepath: + disk_spec.fileOperation = "create" disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add disk_spec.device = vim.vm.device.VirtualDisk() disk_spec.device.backing = \ vim.vm.device.VirtualDisk.FlatVer2BackingInfo() if disk_type == 'thin': disk_spec.device.backing.thinProvisioned = True + if filepath: + disk_spec.device.backing.fileName = filepath disk_spec.device.backing.diskMode = 'persistent' disk_spec.device.unitNumber = unit_number - disk_spec.device.capacityInKB = new_disk_kb + if not filepath: + new_disk_kb = int(disk_size) * 1024 * 1024 + disk_spec.device.capacityInKB = new_disk_kb disk_spec.device.controllerKey = controller.key dev_changes.append(disk_spec) spec.deviceChange = dev_changes task = self.vm.ReconfigVM_Task(spec=spec) - print("Adding a %sGB disk to vm %s" % (disk_size, self.name)) + if disk_size: + print("Adding a %sGB disk to vm %s" % (disk_size, self.name)) + else: + print("Attaching %s disk to vm %s" % (filepath, self.name)) if wait: - ret = self.wait_for_task(task) + ret = self.vc.wait_for_task(task) return ret return task def remove_disk(self, disk_label, retain_file=False, wait=True): + print("Attempt to remove %s from %s" % (disk_label, self.name)) virtual_hdd_device = None if isinstance(disk_label, int): if disk_label < 1: @@ -228,7 +484,7 @@ class VM(VCenter): spec.deviceChange = [virtual_hdd_spec] task = self.vm.ReconfigVM_Task(spec=spec) if wait: - ret = self.wait_for_task(task) + ret = self.vc.wait_for_task(task) return ret return task |