def vim_delete_vm(self, vm_id): """ Delete a VM using the vim client """ try: vim_client = VimClient() vim_client.connect_ticket(self.server, self._get_vim_ticket()) vim_vm = vim_client.get_vm(vm_id) if vim_vm.runtime.powerState != 'poweredOff': try: vim_task = vim_vm.PowerOff() vim_client.wait_for_task(vim_task) except: logger.info("Cannot power off vm", exc_info=True) vim_task = vim_vm.Destroy() vim_client.wait_for_task(vim_task) finally: if vim_client: vim_client.disconnect()
class TestRemoteAgent(unittest.TestCase, AgentCommonTests): def shortDescription(self): return None def get_service_instance(self): # create a connection to hostd. request = ServiceTicketRequest(ServiceType.VIM) response = self.host_client.get_service_ticket(request) self.assertEqual(response.result, ServiceTicketResultCode.OK) hostd_port = 443 vim_namespace = "vim25/5.0" stub = SoapStubAdapter(self.server, hostd_port, vim_namespace) si = vim.ServiceInstance("ServiceInstance", stub) si.RetrieveContent().sessionManager.CloneSession(response.vim_ticket) connect.SetSi(si) return si def connect_client(self, service, cls, server): """ Utility method to connect to a remote agent """ max_sleep_time = 32 sleep_time = 0.1 while sleep_time < max_sleep_time: try: client = DirectClient(service, cls, server, 8835, 60, validate=False) client.connect() return client except TTransport.TTransportException: time.sleep(sleep_time) sleep_time *= 2 self.fail("Cannot connect to agent %s" % server) def create_client(self): return self.connect_client("Host", Host.Client, self.server) def client_connections(self): self.host_client = self.create_client() self.control_client = self.connect_client("AgentControl", AgentControl.Client, self.server) def provision_hosts(self, mem_overcommit=2.0, datastores=None, used_for_vms=True, image_ds=None, host_id=None, deployment_id="test-deployment"): """ Provisions the agents on the remote hosts """ if datastores is None: datastores = self.get_all_datastores() image_datastore = self.get_image_datastore() elif image_ds: image_datastore = image_ds else: image_datastore = datastores[0] req = ProvisionRequest() req.datastores = datastores req.address = ServerAddress(host=self.server, port=8835) req.memory_overcommit = mem_overcommit req.image_datastore_info = ImageDatastore( name=image_datastore, used_for_vms=used_for_vms) req.image_datastores = set([req.image_datastore_info]) req.management_only = True req.auth_enabled = False if host_id: req.host_id = host_id else: req.host_id = self.host_id if deployment_id: req.deployment_id = deployment_id else: req.deployment_id = self.deployment_id res = self.control_client.provision(req) # This will trigger a restart if the agent config changes, which # will happen the first time provision_hosts is called. self.assertEqual(res.result, ProvisionResultCode.OK) # Wait for up to 60 seconds for the agent to reboot. count = 0 while count < 60: try: res = self.control_client.get_agent_status() if res.status == AgentStatusCode.OK: # Agent is up return except: logger.exception("Can't connect to agent") count += 1 time.sleep(1) # Reconnect the clients self._close_agent_connections() self.client_connections() self.fail("Cannot connect to agent %s after provisioning" % self.server) return host_id def setUp(self): from testconfig import config if "agent_remote_test" not in config: raise SkipTest() # Set the default datastore name self._datastores = None if "datastores" in config["agent_remote_test"]: datastores = config["agent_remote_test"]["datastores"] self._datastores = [d.strip() for d in datastores.split(",")] else: self.fail("datastores not provided for test setUp") # Optionally update the specification of a remote iso file. The file # needs to exist on the remote esx server for this test to succeed. self._remote_iso_file = None self._second_remote_iso_file = None if ("iso_file" in config["agent_remote_test"]): self._remote_iso_file = config["agent_remote_test"]["iso_file"] if ("second_iso_file" in config["agent_remote_test"]): self._second_remote_iso_file = config["agent_remote_test"]["second_iso_file"] self.server = config["agent_remote_test"]["server"] self.generation = int(time.time()) # Connect to server and configure vim_client self.client_connections() self.vim_client = VimClient() self.vim_client.connect_ticket(self.server, self._get_vim_ticket()) connect.SetSi(self.vim_client._si) # Set host mode to normal self.set_host_mode(HostMode.NORMAL) # The first time setup is called the agent will restart. self.provision_hosts() # Reconnect to account for the restart self.client_connections() self.clear() @classmethod def setUpClass(cls): cls.host_id = str(uuid.uuid4()) cls.deployment_id = "test-deployment" def _close_agent_connections(self): self.host_client.close() self.control_client.close() def tearDown(self): self._close_agent_connections() self.vim_client.disconnect() def vim_delete_vm(self, vm_id): """ Delete a VM using the vim client """ try: vim_client = VimClient() vim_client.connect_ticket(self.server, self._get_vim_ticket()) vim_vm = vim_client.get_vm(vm_id) if vim_vm.runtime.powerState != 'poweredOff': try: vim_task = vim_vm.PowerOff() vim_client.wait_for_task(vim_task) except: logger.info("Cannot power off vm", exc_info=True) vim_task = vim_vm.Destroy() vim_client.wait_for_task(vim_task) finally: if vim_client: vim_client.disconnect() def clear(self): """Remove all the VMs, disks and images """ request = GetResourcesRequest() response = rpc_call(self.host_client.get_resources, request) assert_that(response.result, is_(GetResourcesResultCode.OK)) for resource in response.resources: delete_request = Host.DeleteVmRequest(vm_id=resource.vm.id, force=True) response = rpc_call(self.host_client.delete_vm, delete_request) if response.result == DeleteVmResultCode.VM_NOT_POWERED_OFF: poweroff_request = Host.PowerVmOpRequest(vm_id=resource.vm.id, op=Host.PowerVmOp.OFF) response = rpc_call(self.host_client.power_vm_op, poweroff_request) assert_that(response.result, is_(PowerVmOpResultCode.OK)) response = rpc_call(self.host_client.delete_vm, delete_request) if response.result != DeleteVmResultCode.OK: logger.info("Cannot delete vm %s trying vim_client" % resource.vm.id) self.vim_delete_vm(resource.vm.id) self.clean_images() def clean_images(self): """ Clean up images if there are any """ datastore = self._find_configured_datastore_in_host_config() request = Host.GetImagesRequest(datastore.id) response = self.host_client.get_images(request) if response.result == GetImagesResultCode.OK: for image_id in response.image_ids: if image_id == "ttylinux": continue # To be removed when we remove ttylinux. logging.info("Cleaning up stray image %s " % image_id) self._delete_image(Image(image_id, datastore)) else: logger.warning("Failed to obtain the list of images to cleanup") def test_send_image_to_host(self): image_id = new_id() + "_test_xfer_image" image_id_2 = "%s_xfered" % image_id dst_image, _ = self._create_test_image(image_id) datastore = self._find_configured_datastore_in_host_config() transfer_image_request = TransferImageRequest( source_image_id=image_id, source_datastore_id=datastore.id, destination_host=ServerAddress(host=self.server, port=8835), destination_datastore_id=datastore.id, destination_image_id=image_id_2) res = self.host_client.transfer_image(transfer_image_request) self.assertEqual(res.result, TransferImageResultCode.OK) # clean up images created in test self._delete_image(dst_image) xfered_image = Image(image_id_2, datastore) self._delete_image(xfered_image) def test_host_config_after_provision(self): """ Test if the agent returns the correct HostConfig after being provisioned """ host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) self.assertEqual(res.result, GetConfigResultCode.OK) hostConfig = res.hostConfig datastores = [ds.name for ds in hostConfig.datastores] containsDs = [ds for ds in self.get_all_datastores() if ds in datastores] self.assertEqual(containsDs, self.get_all_datastores()) networks = [net.id for net in hostConfig.networks] self.assertEqual(networks, self.vim_client.get_networks()) self.assertEqual(hostConfig.address, ServerAddress(host=self.server, port=8835)) self.assertTrue(hostConfig.management_only) # get_host_config reports datastore id for image datastore even if it # was provisioned with a datastore name. image_datastore_name = self.get_image_datastore() image_datastore_id = None for ds in hostConfig.datastores: if ds.name == image_datastore_name: image_datastore_id = ds.id self.assertEqual(list(hostConfig.image_datastore_ids)[0], image_datastore_id) def _generate_new_iso_ds_path(self): if (self._remote_iso_file.lower().rfind(".iso") != len(self._remote_iso_file) - 4): raise ValueError() return "%s-%s.iso" % (self._remote_iso_file[:-4], str(uuid.uuid4())) def _make_new_iso_copy(self, file_manager, new_iso_path): copy_task = file_manager.CopyFile(self._remote_iso_file, None, new_iso_path, None) task.WaitForTask(copy_task) def test_attach_cdrom(self): """ Tests attach iso code path. 1. Attach an iso to a non existent VM. Check correct error 2. Attach a non existent iso file to a valid VM. Check correct error 3. Attach a real iso if specified to a VM. Verify it succeeds. Test should pass the iso path as [datastore_name]/path/to/iso.iso """ if not self._remote_iso_file: raise SkipTest("ISO file on server not provided") si = self.get_service_instance() file_manager = si.RetrieveContent().fileManager iso_path = self._generate_new_iso_ds_path() iso_path_2 = self._generate_new_iso_ds_path() vm_wrapper = VmWrapper(self.host_client) image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), "default", False, True, image=image, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), ] # Create disk and VM. reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # Verify the result when the VM is not found. fake_id = str(uuid.uuid4()) vm_wrapper.attach_iso(fake_id, "/tmp/foo.iso", Host.AttachISOResultCode.VM_NOT_FOUND) # Verify the result when the the iso doesn't exist. vm_wrapper.attach_iso(vm_id, "/tmp/foo.iso", Host.AttachISOResultCode.SYSTEM_ERROR) self._make_new_iso_copy(file_manager, iso_path) self._make_new_iso_copy(file_manager, iso_path_2) # Doing enough attaches will indirectly verify that we do not grow the # device list on reattach. for i in xrange(3): # verify attach works vm_wrapper.attach_iso(vm_id, iso_path) # verify re-attach to another iso works vm_wrapper.attach_iso(vm_id, iso_path_2) vm_wrapper.power(Host.PowerVmOp.ON) # Verify reattach fails when vm is powered on. vm_wrapper.attach_iso(vm_id, iso_path, Host.AttachISOResultCode.ISO_ATTACHED_ERROR) vm_wrapper.power(Host.PowerVmOp.OFF) vm_wrapper.detach_iso(vm_id, True) vm_wrapper.attach_iso(vm_id, iso_path) vm_wrapper.detach_iso(vm_id, True) self.clear() def test_detach_cdrom_failure(self): """ Tests failures of detach iso from VM. """ vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # no prior attach of iso vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.ISO_NOT_ATTACHED) # nonexistent VM id fake_id = str(uuid.uuid4()) vm_wrapper.detach_iso(fake_id, True, Host.DetachISOResultCode.VM_NOT_FOUND) # Attaching nonexistent iso path still should succeed as long # as a valid datastore path format is used random = str(uuid.uuid4()) vm_wrapper.attach_iso(vm_id, "[] /tmp/%s_nonexistent_.iso" % random, Host.AttachISOResultCode.OK) # Not supporting detach without delete yet. vm_wrapper.detach_iso(vm_id, False, Host.DetachISOResultCode.SYSTEM_ERROR) # But detach a non-exist iso should work. vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.delete(request=vm_wrapper.delete_request()) def test_detach_cdrom(self): """ Tests detach iso from VM. Verify Detaching a real iso from a VM. """ if not self._remote_iso_file: raise SkipTest("ISO file on server not provided") si = self.get_service_instance() file_manager = si.RetrieveContent().fileManager iso_path = self._generate_new_iso_ds_path() self._make_new_iso_copy(file_manager, iso_path) iso_path_2 = self._generate_new_iso_ds_path() self._make_new_iso_copy(file_manager, iso_path_2) vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id vm_wrapper.attach_iso(vm_id, iso_path, Host.AttachISOResultCode.OK) vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.attach_iso(vm_id, iso_path_2, Host.AttachISOResultCode.OK) # verify detach works when powered on vm_wrapper.power(Host.PowerVmOp.ON) vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.power(Host.PowerVmOp.OFF) vm_wrapper.delete(request=vm_wrapper.delete_request()) def test_remote_boostrap(self): """ Tests boostrapping of an agent against a real host """ # We need to be able to read the config from the host to set it # correctly. # https://www.pivotaltracker.com/story/show/83243144 raise SkipTest() req = self._update_agent_config() # Try connecting to the client in a loop. # Back off on failure to connect to agent max_sleep_time = 32 sleep_time = 0.1 while sleep_time < max_sleep_time: try: self.host_client.connect() break except TTransport.TTransportException: time.sleep(sleep_time) sleep_time *= 2 self._validate_post_boostrap_config(req) def test_get_nfc_ticket_with_ds_id(self): datastores = self.vim_client.get_all_datastores() image_datastore = [ds for ds in datastores if ds.name == self.get_image_datastore()][0] request = ServiceTicketRequest(service_type=ServiceType.NFC, datastore_name=image_datastore.id) response = self.host_client.get_service_ticket(request) assert_that(response.result, is_(ServiceTicketResultCode.OK)) ticket = response.ticket assert_that(ticket, not_none()) assert_that(ticket.port, is_(902)) assert_that(ticket.service_type, is_("nfc")) assert_that(ticket.session_id, not_none()) assert_that(ticket.ssl_thumbprint, not_none()) def test_persist_mode(self): # Enter maintenance self.set_host_mode(HostMode.MAINTENANCE) # Restart agent by provisioning with different configuration self.provision_hosts(mem_overcommit=2.1) self.client_connections() # Check mode. It should still be MAINTENANCE. response = self.host_client.get_host_mode(GetHostModeRequest()) assert_that(response.result, equal_to(GetHostModeResultCode.OK)) assert_that(response.mode, equal_to(HostMode.MAINTENANCE)) def _create_test_image(self, name): """ Create an test image for tests to use on a datastore """ datastore = self._find_configured_datastore_in_host_config() # ttylinux is the default image that is copied to datastore # when agent starts src_image = Image("ttylinux", datastore) dst_image = Image(name, datastore) # Copy image request = Host.CopyImageRequest(src_image, dst_image) response = self.host_client.copy_image(request) assert_that(response.result, is_in([CopyImageResultCode.OK, CopyImageResultCode.DESTINATION_ALREADY_EXIST])) return dst_image, datastore def _get_vim_ticket(self): request = ServiceTicketRequest(ServiceType.VIM) response = self.host_client.get_service_ticket(request) assert_that(response.result, is_(ServiceTicketResultCode.OK)) return response.vim_ticket def test_create_vm_with_ephemeral_disks_concurrent(self): concurrency = 5 atmoic_lock = threading.Lock() results = {"count": 0} def _thread(): self._test_create_vm_with_ephemeral_disks("ttylinux", concurrent=True, new_client=True) with atmoic_lock: results["count"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() assert_that(results["count"], is_(concurrency)) def test_concurrent_copy_image(self): concurrency = 3 atomic_lock = threading.Lock() results = {"ok": 0, "existed": 0} datastore = self._find_configured_datastore_in_host_config() new_image_id = "concurrent-copy-%s" % str(uuid.uuid4()) src_image = Image("ttylinux", datastore) dst_image = Image(new_image_id, datastore) # verify destination_id is not in datastore request = Host.GetImagesRequest(datastore.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item("ttylinux")) assert_that(response.image_ids, not(has_item(new_image_id))) image_number = len(response.image_ids) def _thread(): client = self.create_client() request = Host.CopyImageRequest(src_image, dst_image) response = client.copy_image(request) ok = response.result == CopyImageResultCode.OK existed = response.result == CopyImageResultCode.\ DESTINATION_ALREADY_EXIST # Verify destination_id is in datastore request = Host.GetImagesRequest(datastore.id) response = client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item("ttylinux")) assert_that(response.image_ids, has_item(new_image_id)) assert_that(response.image_ids, has_length(image_number + 1)) with atomic_lock: if ok: results["ok"] += 1 if existed: results["existed"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() # Clean destination image self._delete_image(dst_image) # Only one copy is successful, all others return # DESTINATION_ALREADY_EXIST assert_that(results["ok"], is_(1)) assert_that(results["existed"], is_(concurrency - 1)) def test_force_delete_vm(self): vm_wrapper = VmWrapper(self.host_client) # create a vm without disk reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # create 2 disks disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR) ] for disk in disks: reservation = vm_wrapper.place_and_reserve(disk=disk).reservation vm_wrapper.create_disk(disk, reservation, validate=True) # attach disks disk_ids = [disk.id for disk in disks] vm_wrapper.attach_disks(vm_id, disk_ids) # delete vm fails without force vm_wrapper.delete(request=vm_wrapper.delete_request(), expect=Host.DeleteVmResultCode.OPERATION_NOT_ALLOWED) # delete vm with force succeeds vm_wrapper.delete(request=vm_wrapper.delete_request(force=True)) for disk_id in disk_ids: vm_wrapper.get_disk(disk_id, expect_found=False) def test_disk_uuids(self): # Create a vm without a root disk and blank disk then attach another # persistent disk. Then verify that only the uuids of the # ephemeral and persistent disks are updated to match their cloud ids. vm_wrapper = VmWrapper(self.host_client) disk_id_root = new_id() disk_id_ephemeral = new_id() disk_id_persistent = new_id() image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(disk_id_root, self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(disk_id_ephemeral, self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), ] reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # create one persistent disk disk = Disk(disk_id_persistent, self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR) reservation = vm_wrapper.place_and_reserve(disk=disk).reservation vm_wrapper.create_disk(disk, reservation, validate=True) vm_wrapper.attach_disks(vm_id, [disk_id_persistent]) vim_vm = self.vim_client.get_vm(vm_id) disk_uuid_map = dict([(dev.backing.uuid, dev.backing.fileName) for dev in vim_vm.config.hardware.device if isinstance(dev, vim.vm.device.VirtualDisk)]) # Assert that the UUID assigned to the ephemeral and persistent disks # matches their ids for disk_id in (disk_id_ephemeral, disk_id_persistent): self.assertTrue(disk_id in disk_uuid_map and disk_id in disk_uuid_map[disk_id]) # Assert that no such assignment is done for link-clone root disk. self.assertFalse(disk_id_root in disk_uuid_map) vm_wrapper.detach_disks(vm_id, [disk_id_persistent]) vm_wrapper.delete(request=vm_wrapper.delete_request()) vm_wrapper.delete_disks([disk_id_persistent], validate=True) def test_place_on_multiple_datastores(self): """ Test placement can actually place vm to datastores without image. """ host_datastores = self.vim_client.get_all_datastores() image_datastore = self._find_configured_datastore_in_host_config() dest_datastore = None for ds in host_datastores: if ds.id != image_datastore.id: dest_datastore = ds break if not dest_datastore: raise SkipTest() # Test only 2 datastores, with one image datastore and another # datastore. self.provision_hosts(datastores=[image_datastore.name, dest_datastore.name], used_for_vms=False) self.client_connections() concurrency = 3 atmoic_lock = threading.Lock() results = {"count": 0} # Only copy image to datastore[0] new_image_id = str(uuid.uuid4()) datastore = Datastore(id=image_datastore.name) src_image = Image("ttylinux", datastore) dst_image = Image(new_image_id, datastore) request = Host.CopyImageRequest(src_image, dst_image) response = self.host_client.copy_image(request) assert_that(response.result, is_(CopyImageResultCode.OK)) def _thread(): self._test_create_vm_with_ephemeral_disks(new_image_id, concurrent=True, new_client=True) with atmoic_lock: results["count"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() # Make sure the new image is copied to both datastores, and clean # them up. for ds in (image_datastore, dest_datastore): image = Image(datastore=Datastore(id=ds.name), id=new_image_id) self._delete_image(image) assert_that(results["count"], is_(concurrency)) def test_place_on_datastore_tag(self): host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) self.assertEqual(res.result, GetConfigResultCode.OK) datastores = res.hostConfig.datastores for datastore in datastores: tag = self._type_to_tag(datastore.type) if not tag: continue vm_wrapper = VmWrapper(self.host_client) # Test place disks with only datastore constraint disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], []) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints) # Test place disks with datastore and datastore tag constraint disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], [tag]) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints, expect=PlaceResultCode.OK) # Test place disks with the wrong datastore tag for other_tag in self._other_tags(tag): disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], [other_tag]) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints, expect=PlaceResultCode.NO_SUCH_RESOURCE) def test_provision_without_datastores(self): """ Test that the host uses all the datastores when it gets provisioned without any datastores specified. """ # provision the host without datastores datastores = self.get_all_datastores() self.provision_hosts(datastores=[], image_ds=datastores[0]) # verify that the host configuration contains all the datastores. req = Host.GetConfigRequest() res = self.create_client().get_host_config(req) self.assertEqual(len(res.hostConfig.datastores), len(self.vim_client.get_all_datastores())) def _manage_disk(self, op, **kwargs): task = op(self.vim_client._content.virtualDiskManager, **kwargs) self.vim_client.wait_for_task(task) def _gen_vd_spec(self): spec = vim.VirtualDiskManager.VirtualDiskSpec() spec.disk_type = str(vim.VirtualDiskManager.VirtualDiskType.thin) spec.adapterType = str(vim.VirtualDiskManager.VirtualDiskAdapterType.lsiLogic) return spec def test_finalize_image(self): """ Integration test for atomic image create """ img_id = "test-create-image" tmp_img_id = "-tmp-" + img_id tmp_image, ds = self._create_test_image(tmp_img_id) tmp_image_path = datastore_path(ds.name, "image_" + tmp_img_id) src_vmdk = vmdk_path(ds.id, tmp_img_id, IMAGE_FOLDER_NAME_PREFIX) dst_vmdk = "%s/%s.vmdk" % (tmp_image_path, img_id) try: self._manage_disk( vim.VirtualDiskManager.MoveVirtualDisk_Task, sourceName=src_vmdk, destName=dst_vmdk, force=True) except: logger.error("Error moving vmdk %s" % src_vmdk, exc_info=True) self._manage_disk( vim.VirtualDiskManager.DeleteVirtualDisk_Task, name=src_vmdk) raise dst_image = Image(img_id, ds) req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.OK) request = Host.GetImagesRequest(ds.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item(img_id)) # Issue another create call and it should fail as the source doesn't # exist. req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.IMAGE_NOT_FOUND) # Verify that we fail if the destination already exists. tmp_image, ds = self._create_test_image(tmp_img_id) req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.DESTINATION_ALREADY_EXIST) # cleanup self._delete_image(dst_image) def test_start_image_scanner(self): """ Test image scanner. Make sure the idle images are reported correctly. """ datastore = self._find_configured_datastore_in_host_config() image_id_1 = new_id() dst_image_1, _ = self._create_test_image(image_id_1) image_id_2 = new_id() dst_image_2, _ = self._create_test_image(image_id_2) disk_image_2 = DiskImage(image_id_2, CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=disk_image_2, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=2, flavor_info=self.DEFAULT_DISK_FLAVOR) ] vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_wrapper.create(request=request) logger.info("Image scan, Vm id: %s" % vm_wrapper.id) # Start image scanner start_scan_request = StartImageScanRequest() start_scan_request.datastore_id = datastore.id start_scan_request.scan_rate = 600 start_scan_request.timeout = 30 start_scan_response = self.host_client.start_image_scan(start_scan_request) self.assertEqual(start_scan_response.result, StartImageOperationResultCode.OK) self._get_and_check_inactive_images(datastore.id, image_id_1, True) get_inactive_images_response = self._get_and_check_inactive_images(datastore.id, image_id_2, False) # Start image sweeper start_sweep_request = StartImageSweepRequest() start_sweep_request.datastore_id = datastore.id start_sweep_request.image_descs = get_inactive_images_response.image_descs start_sweep_request.sweep_rate = 600 start_sweep_request.timeout = 30 start_sweep_request.grace_period = 0 start_sweep_response = self.host_client.start_image_sweep(start_sweep_request) self.assertEqual(start_sweep_response.result, StartImageOperationResultCode.OK) self._get_and_check_deleted_images(datastore.id, image_id_1, True) # cleanup vm_wrapper.delete() self._delete_image(dst_image_1, DeleteDirectoryResultCode.DIRECTORY_NOT_FOUND) self._delete_image(dst_image_2) def _get_and_check_inactive_images(self, datastore_id, image_id, found): get_inactive_images_request = GetInactiveImagesRequest() get_inactive_images_request.datastore_id = datastore_id for counter in range(1, 30): time.sleep(1) get_inactive_images_response = self.host_client.get_inactive_images(get_inactive_images_request) if get_inactive_images_response.result is GetMonitoredImagesResultCode.OK: break self.assertEqual(get_inactive_images_response.result, GetMonitoredImagesResultCode.OK) image_descriptors = get_inactive_images_response.image_descs image_found = False logger.info("Image Descriptors: %s" % image_descriptors) logger.info("Target Image Id: %s" % image_id) for image_descriptor in image_descriptors: if image_descriptor.image_id == image_id: image_found = True self.assertEqual(image_found, found) return get_inactive_images_response def _get_and_check_deleted_images(self, datastore_id, image_id, found): get_deleted_images_request = GetInactiveImagesRequest() get_deleted_images_request.datastore_id = datastore_id for counter in range(1, 30): time.sleep(1) get_inactive_deleted_response = self.host_client.get_deleted_images(get_deleted_images_request) if get_inactive_deleted_response.result is GetMonitoredImagesResultCode.OK: break self.assertEqual(get_inactive_deleted_response.result, GetMonitoredImagesResultCode.OK) image_descriptors = get_inactive_deleted_response.image_descs image_found = False logger.info("Image Descriptors: %s" % image_descriptors) logger.info("Target Image Id: %s" % image_id) for image_descriptor in image_descriptors: if image_descriptor.image_id == image_id: image_found = True self.assertEqual(image_found, found) return get_inactive_deleted_response def _type_to_tag(self, type): type_to_tag = { DatastoreType.NFS_3: NFS_TAG, DatastoreType.NFS_41: NFS_TAG, DatastoreType.SHARED_VMFS: SHARED_VMFS_TAG, DatastoreType.LOCAL_VMFS: LOCAL_VMFS_TAG, DatastoreType.LOCAL_VMFS: VSAN_TAG, } if type in type_to_tag: return type_to_tag[type] else: return None def _other_tags(self, tag): tags = [NFS_TAG, SHARED_VMFS_TAG, LOCAL_VMFS_TAG, VSAN_TAG] tags.remove(tag) return tags def _create_constraints(self, datastores, tags): constraints = [] for datastore in datastores: constraints.append(ResourceConstraint( type=ResourceConstraintType.DATASTORE, values=[datastore])) for tag in tags: constraints.append(ResourceConstraint( type=ResourceConstraintType.DATASTORE_TAG, values=[tag])) return constraints def test_create_image_from_vm(self): """ Integration test for creating an image from a VM """ img_id = "test-new-im-from-vm-%s" % new_id() tmp_img_id = "-tmp-" + img_id tmp_image, ds = self._create_test_image(tmp_img_id) tmp_image_path = datastore_path(ds.id, "image_" + tmp_img_id) src_vmdk = vmdk_path(ds.id, tmp_img_id, IMAGE_FOLDER_NAME_PREFIX) vm_wrapper = VmWrapper(self.host_client) try: self._manage_disk( vim.VirtualDiskManager.DeleteVirtualDisk_Task, name=src_vmdk) except: logger.error( "Error deleting vmdk when setting up tmp image %s" % src_vmdk, exc_info=True) raise dst_image = Image(img_id, ds) image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=2, flavor_info=self.DEFAULT_DISK_FLAVOR) ] reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_wrapper.create(request=request) # VM in wrong state vm_wrapper.power(Host.PowerVmOp.ON, Host.PowerVmOpResultCode.OK) time.sleep(10) vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.INVALID_VM_POWER_STATE) vm_wrapper.power(Host.PowerVmOp.OFF, Host.PowerVmOpResultCode.OK) time.sleep(10) # Happy case vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.OK) request = Host.GetImagesRequest(ds.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item(img_id)) # Issue another create call and it should fail as the source doesn't # exist. req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.IMAGE_NOT_FOUND) # Verify that we fail if the destination already exists. tmp_image, ds = self._create_test_image(tmp_img_id) vm_wrapper.create_image_from_vm( image_id=tmp_img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.IMAGE_ALREADY_EXIST) vm_wrapper.delete() # VM to create image from is gone. vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.VM_NOT_FOUND) # Create a VM using the new image created vm_wrapper2 = VmWrapper(self.host_client) image = DiskImage(img_id, CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), ] reservation = vm_wrapper2.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper2.create_request(res_id=reservation) vm_wrapper2.create(request=request) vm_wrapper2.power(Host.PowerVmOp.ON, Host.PowerVmOpResultCode.OK) vm_wrapper2.power(Host.PowerVmOp.OFF, Host.PowerVmOpResultCode.OK) vm_wrapper2.delete() # cleanup self._delete_image(dst_image) def test_delete_tmp_image(self): """ Integration test for deleting temp image directory """ img_id = "test-delete-tmp-image" tmp_iamge, ds = self._create_test_image(img_id) tmp_image_path = "image_" + img_id req = DeleteDirectoryRequest(datastore=ds.id, directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.OK) req = DeleteDirectoryRequest(datastore=ds.id, directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.DIRECTORY_NOT_FOUND) req = DeleteDirectoryRequest(datastore="foo_bar", directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.DATASTORE_NOT_FOUND) def _get_agent_id(self): host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) return res.hostConfig.agent_id
class TestRemoteAgent(unittest.TestCase, AgentCommonTests): def shortDescription(self): return None def get_service_instance(self): # create a connection to hostd. request = ServiceTicketRequest(ServiceType.VIM) response = self.host_client.get_service_ticket(request) self.assertEqual(response.result, ServiceTicketResultCode.OK) hostd_port = 443 vim_namespace = "vim25/5.0" stub = SoapStubAdapter(self.server, hostd_port, vim_namespace) si = vim.ServiceInstance("ServiceInstance", stub) si.RetrieveContent().sessionManager.CloneSession(response.vim_ticket) connect.SetSi(si) return si def connect_client(self, service, cls, server): """ Utility method to connect to a remote agent """ max_sleep_time = 32 sleep_time = 0.1 while sleep_time < max_sleep_time: try: client = DirectClient(service, cls, server, 8835, 60, validate=False) client.connect() return client except TTransport.TTransportException: time.sleep(sleep_time) sleep_time *= 2 self.fail("Cannot connect to agent %s" % server) def create_client(self): return self.connect_client("Host", Host.Client, self.server) def client_connections(self): self.host_client = self.create_client() self.control_client = self.connect_client("AgentControl", AgentControl.Client, self.server) def provision_hosts(self, mem_overcommit=2.0, datastores=None, used_for_vms=True, image_ds=None, host_id=None, deployment_id="test-deployment"): """ Provisions the agents on the remote hosts """ if datastores is None: datastores = self.get_all_datastores() image_datastore = self.get_image_datastore() elif image_ds: image_datastore = image_ds else: image_datastore = datastores[0] req = ProvisionRequest() req.datastores = datastores req.address = ServerAddress(host=self.server, port=8835) req.memory_overcommit = mem_overcommit req.image_datastore_info = ImageDatastore(name=image_datastore, used_for_vms=used_for_vms) req.image_datastores = set([req.image_datastore_info]) req.management_only = True req.auth_enabled = False if host_id: req.host_id = host_id else: req.host_id = self.host_id if deployment_id: req.deployment_id = deployment_id else: req.deployment_id = self.deployment_id res = self.control_client.provision(req) # This will trigger a restart if the agent config changes, which # will happen the first time provision_hosts is called. self.assertEqual(res.result, ProvisionResultCode.OK) # Wait for up to 60 seconds for the agent to reboot. count = 0 while count < 60: try: res = self.control_client.get_agent_status() if res.status == AgentStatusCode.OK: # Agent is up return except: logger.exception("Can't connect to agent") count += 1 time.sleep(1) # Reconnect the clients self._close_agent_connections() self.client_connections() self.fail("Cannot connect to agent %s after provisioning" % self.server) return host_id def setUp(self): from testconfig import config if "agent_remote_test" not in config: raise SkipTest() # Set the default datastore name self._datastores = None if "datastores" in config["agent_remote_test"]: datastores = config["agent_remote_test"]["datastores"] self._datastores = [d.strip() for d in datastores.split(",")] else: self.fail("datastores not provided for test setUp") # Optionally update the specification of a remote iso file. The file # needs to exist on the remote esx server for this test to succeed. self._remote_iso_file = None self._second_remote_iso_file = None if ("iso_file" in config["agent_remote_test"]): self._remote_iso_file = config["agent_remote_test"]["iso_file"] if ("second_iso_file" in config["agent_remote_test"]): self._second_remote_iso_file = config["agent_remote_test"][ "second_iso_file"] self.server = config["agent_remote_test"]["server"] self.generation = int(time.time()) # Connect to server and configure vim_client self.client_connections() self.vim_client = VimClient() self.vim_client.connect_ticket(self.server, self._get_vim_ticket()) connect.SetSi(self.vim_client._si) # Set host mode to normal self.set_host_mode(HostMode.NORMAL) # The first time setup is called the agent will restart. self.provision_hosts() # Reconnect to account for the restart self.client_connections() self.clear() @classmethod def setUpClass(cls): cls.host_id = str(uuid.uuid4()) cls.deployment_id = "test-deployment" def _close_agent_connections(self): self.host_client.close() self.control_client.close() def tearDown(self): self._close_agent_connections() self.vim_client.disconnect() def vim_delete_vm(self, vm_id): """ Delete a VM using the vim client """ try: vim_client = VimClient() vim_client.connect_ticket(self.server, self._get_vim_ticket()) vim_vm = vim_client.get_vm(vm_id) if vim_vm.runtime.powerState != 'poweredOff': try: vim_task = vim_vm.PowerOff() vim_client.wait_for_task(vim_task) except: logger.info("Cannot power off vm", exc_info=True) vim_task = vim_vm.Destroy() vim_client.wait_for_task(vim_task) finally: if vim_client: vim_client.disconnect() def clear(self): """Remove all the VMs, disks and images """ request = GetResourcesRequest() response = rpc_call(self.host_client.get_resources, request) assert_that(response.result, is_(GetResourcesResultCode.OK)) for resource in response.resources: delete_request = Host.DeleteVmRequest(vm_id=resource.vm.id, force=True) response = rpc_call(self.host_client.delete_vm, delete_request) if response.result == DeleteVmResultCode.VM_NOT_POWERED_OFF: poweroff_request = Host.PowerVmOpRequest(vm_id=resource.vm.id, op=Host.PowerVmOp.OFF) response = rpc_call(self.host_client.power_vm_op, poweroff_request) assert_that(response.result, is_(PowerVmOpResultCode.OK)) response = rpc_call(self.host_client.delete_vm, delete_request) if response.result != DeleteVmResultCode.OK: logger.info("Cannot delete vm %s trying vim_client" % resource.vm.id) self.vim_delete_vm(resource.vm.id) self.clean_images() def clean_images(self): """ Clean up images if there are any """ datastore = self._find_configured_datastore_in_host_config() request = Host.GetImagesRequest(datastore.id) response = self.host_client.get_images(request) if response.result == GetImagesResultCode.OK: for image_id in response.image_ids: if image_id == "ttylinux": continue # To be removed when we remove ttylinux. logging.info("Cleaning up stray image %s " % image_id) self._delete_image(Image(image_id, datastore)) else: logger.warning("Failed to obtain the list of images to cleanup") def test_send_image_to_host(self): image_id = new_id() + "_test_xfer_image" image_id_2 = "%s_xfered" % image_id dst_image, _ = self._create_test_image(image_id) datastore = self._find_configured_datastore_in_host_config() transfer_image_request = TransferImageRequest( source_image_id=image_id, source_datastore_id=datastore.id, destination_host=ServerAddress(host=self.server, port=8835), destination_datastore_id=datastore.id, destination_image_id=image_id_2) res = self.host_client.transfer_image(transfer_image_request) self.assertEqual(res.result, TransferImageResultCode.OK) # clean up images created in test self._delete_image(dst_image) xfered_image = Image(image_id_2, datastore) self._delete_image(xfered_image) def test_host_config_after_provision(self): """ Test if the agent returns the correct HostConfig after being provisioned """ host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) self.assertEqual(res.result, GetConfigResultCode.OK) hostConfig = res.hostConfig datastores = [ds.name for ds in hostConfig.datastores] containsDs = [ ds for ds in self.get_all_datastores() if ds in datastores ] self.assertEqual(containsDs, self.get_all_datastores()) networks = [net.id for net in hostConfig.networks] self.assertEqual(networks, self.vim_client.get_networks()) self.assertEqual(hostConfig.address, ServerAddress(host=self.server, port=8835)) self.assertTrue(hostConfig.management_only) # get_host_config reports datastore id for image datastore even if it # was provisioned with a datastore name. image_datastore_name = self.get_image_datastore() image_datastore_id = None for ds in hostConfig.datastores: if ds.name == image_datastore_name: image_datastore_id = ds.id self.assertEqual( list(hostConfig.image_datastore_ids)[0], image_datastore_id) def _generate_new_iso_ds_path(self): if (self._remote_iso_file.lower().rfind(".iso") != len(self._remote_iso_file) - 4): raise ValueError() return "%s-%s.iso" % (self._remote_iso_file[:-4], str(uuid.uuid4())) def _make_new_iso_copy(self, file_manager, new_iso_path): copy_task = file_manager.CopyFile(self._remote_iso_file, None, new_iso_path, None) task.WaitForTask(copy_task) def test_attach_cdrom(self): """ Tests attach iso code path. 1. Attach an iso to a non existent VM. Check correct error 2. Attach a non existent iso file to a valid VM. Check correct error 3. Attach a real iso if specified to a VM. Verify it succeeds. Test should pass the iso path as [datastore_name]/path/to/iso.iso """ if not self._remote_iso_file: raise SkipTest("ISO file on server not provided") si = self.get_service_instance() file_manager = si.RetrieveContent().fileManager iso_path = self._generate_new_iso_ds_path() iso_path_2 = self._generate_new_iso_ds_path() vm_wrapper = VmWrapper(self.host_client) image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), "default", False, True, image=image, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), ] # Create disk and VM. reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # Verify the result when the VM is not found. fake_id = str(uuid.uuid4()) vm_wrapper.attach_iso(fake_id, "/tmp/foo.iso", Host.AttachISOResultCode.VM_NOT_FOUND) # Verify the result when the the iso doesn't exist. vm_wrapper.attach_iso(vm_id, "/tmp/foo.iso", Host.AttachISOResultCode.SYSTEM_ERROR) self._make_new_iso_copy(file_manager, iso_path) self._make_new_iso_copy(file_manager, iso_path_2) # Doing enough attaches will indirectly verify that we do not grow the # device list on reattach. for i in xrange(3): # verify attach works vm_wrapper.attach_iso(vm_id, iso_path) # verify re-attach to another iso works vm_wrapper.attach_iso(vm_id, iso_path_2) vm_wrapper.power(Host.PowerVmOp.ON) # Verify reattach fails when vm is powered on. vm_wrapper.attach_iso(vm_id, iso_path, Host.AttachISOResultCode.ISO_ATTACHED_ERROR) vm_wrapper.power(Host.PowerVmOp.OFF) vm_wrapper.detach_iso(vm_id, True) vm_wrapper.attach_iso(vm_id, iso_path) vm_wrapper.detach_iso(vm_id, True) self.clear() def test_detach_cdrom_failure(self): """ Tests failures of detach iso from VM. """ vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # no prior attach of iso vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.ISO_NOT_ATTACHED) # nonexistent VM id fake_id = str(uuid.uuid4()) vm_wrapper.detach_iso(fake_id, True, Host.DetachISOResultCode.VM_NOT_FOUND) # Attaching nonexistent iso path still should succeed as long # as a valid datastore path format is used random = str(uuid.uuid4()) vm_wrapper.attach_iso(vm_id, "[] /tmp/%s_nonexistent_.iso" % random, Host.AttachISOResultCode.OK) # Not supporting detach without delete yet. vm_wrapper.detach_iso(vm_id, False, Host.DetachISOResultCode.SYSTEM_ERROR) # But detach a non-exist iso should work. vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.delete(request=vm_wrapper.delete_request()) def test_detach_cdrom(self): """ Tests detach iso from VM. Verify Detaching a real iso from a VM. """ if not self._remote_iso_file: raise SkipTest("ISO file on server not provided") si = self.get_service_instance() file_manager = si.RetrieveContent().fileManager iso_path = self._generate_new_iso_ds_path() self._make_new_iso_copy(file_manager, iso_path) iso_path_2 = self._generate_new_iso_ds_path() self._make_new_iso_copy(file_manager, iso_path_2) vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id vm_wrapper.attach_iso(vm_id, iso_path, Host.AttachISOResultCode.OK) vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.attach_iso(vm_id, iso_path_2, Host.AttachISOResultCode.OK) # verify detach works when powered on vm_wrapper.power(Host.PowerVmOp.ON) vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.power(Host.PowerVmOp.OFF) vm_wrapper.delete(request=vm_wrapper.delete_request()) def test_remote_boostrap(self): """ Tests boostrapping of an agent against a real host """ # We need to be able to read the config from the host to set it # correctly. # https://www.pivotaltracker.com/story/show/83243144 raise SkipTest() req = self._update_agent_config() # Try connecting to the client in a loop. # Back off on failure to connect to agent max_sleep_time = 32 sleep_time = 0.1 while sleep_time < max_sleep_time: try: self.host_client.connect() break except TTransport.TTransportException: time.sleep(sleep_time) sleep_time *= 2 self._validate_post_boostrap_config(req) def test_get_nfc_ticket_with_ds_id(self): datastores = self.vim_client.get_all_datastores() image_datastore = [ ds for ds in datastores if ds.name == self.get_image_datastore() ][0] request = ServiceTicketRequest(service_type=ServiceType.NFC, datastore_name=image_datastore.id) response = self.host_client.get_service_ticket(request) assert_that(response.result, is_(ServiceTicketResultCode.OK)) ticket = response.ticket assert_that(ticket, not_none()) assert_that(ticket.port, is_(902)) assert_that(ticket.service_type, is_("nfc")) assert_that(ticket.session_id, not_none()) assert_that(ticket.ssl_thumbprint, not_none()) def test_persist_mode(self): # Enter maintenance self.set_host_mode(HostMode.MAINTENANCE) # Restart agent by provisioning with different configuration self.provision_hosts(mem_overcommit=2.1) self.client_connections() # Check mode. It should still be MAINTENANCE. response = self.host_client.get_host_mode(GetHostModeRequest()) assert_that(response.result, equal_to(GetHostModeResultCode.OK)) assert_that(response.mode, equal_to(HostMode.MAINTENANCE)) def _create_test_image(self, name): """ Create an test image for tests to use on a datastore """ datastore = self._find_configured_datastore_in_host_config() # ttylinux is the default image that is copied to datastore # when agent starts src_image = Image("ttylinux", datastore) dst_image = Image(name, datastore) # Copy image request = Host.CopyImageRequest(src_image, dst_image) response = self.host_client.copy_image(request) assert_that( response.result, is_in([ CopyImageResultCode.OK, CopyImageResultCode.DESTINATION_ALREADY_EXIST ])) return dst_image, datastore def _get_vim_ticket(self): request = ServiceTicketRequest(ServiceType.VIM) response = self.host_client.get_service_ticket(request) assert_that(response.result, is_(ServiceTicketResultCode.OK)) return response.vim_ticket def test_create_vm_with_ephemeral_disks_concurrent(self): concurrency = 5 atmoic_lock = threading.Lock() results = {"count": 0} def _thread(): self._test_create_vm_with_ephemeral_disks("ttylinux", concurrent=True, new_client=True) with atmoic_lock: results["count"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() assert_that(results["count"], is_(concurrency)) def test_concurrent_copy_image(self): concurrency = 3 atomic_lock = threading.Lock() results = {"ok": 0, "existed": 0} datastore = self._find_configured_datastore_in_host_config() new_image_id = "concurrent-copy-%s" % str(uuid.uuid4()) src_image = Image("ttylinux", datastore) dst_image = Image(new_image_id, datastore) # verify destination_id is not in datastore request = Host.GetImagesRequest(datastore.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item("ttylinux")) assert_that(response.image_ids, not (has_item(new_image_id))) image_number = len(response.image_ids) def _thread(): client = self.create_client() request = Host.CopyImageRequest(src_image, dst_image) response = client.copy_image(request) ok = response.result == CopyImageResultCode.OK existed = response.result == CopyImageResultCode.\ DESTINATION_ALREADY_EXIST # Verify destination_id is in datastore request = Host.GetImagesRequest(datastore.id) response = client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item("ttylinux")) assert_that(response.image_ids, has_item(new_image_id)) assert_that(response.image_ids, has_length(image_number + 1)) with atomic_lock: if ok: results["ok"] += 1 if existed: results["existed"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() # Clean destination image self._delete_image(dst_image) # Only one copy is successful, all others return # DESTINATION_ALREADY_EXIST assert_that(results["ok"], is_(1)) assert_that(results["existed"], is_(concurrency - 1)) def test_force_delete_vm(self): vm_wrapper = VmWrapper(self.host_client) # create a vm without disk reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # create 2 disks disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR) ] for disk in disks: reservation = vm_wrapper.place_and_reserve(disk=disk).reservation vm_wrapper.create_disk(disk, reservation, validate=True) # attach disks disk_ids = [disk.id for disk in disks] vm_wrapper.attach_disks(vm_id, disk_ids) # delete vm fails without force vm_wrapper.delete(request=vm_wrapper.delete_request(), expect=Host.DeleteVmResultCode.OPERATION_NOT_ALLOWED) # delete vm with force succeeds vm_wrapper.delete(request=vm_wrapper.delete_request(force=True)) for disk_id in disk_ids: vm_wrapper.get_disk(disk_id, expect_found=False) def test_disk_uuids(self): # Create a vm without a root disk and blank disk then attach another # persistent disk. Then verify that only the uuids of the # ephemeral and persistent disks are updated to match their cloud ids. vm_wrapper = VmWrapper(self.host_client) disk_id_root = new_id() disk_id_ephemeral = new_id() disk_id_persistent = new_id() image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(disk_id_root, self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(disk_id_ephemeral, self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), ] reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # create one persistent disk disk = Disk(disk_id_persistent, self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR) reservation = vm_wrapper.place_and_reserve(disk=disk).reservation vm_wrapper.create_disk(disk, reservation, validate=True) vm_wrapper.attach_disks(vm_id, [disk_id_persistent]) vim_vm = self.vim_client.get_vm(vm_id) disk_uuid_map = dict([(dev.backing.uuid, dev.backing.fileName) for dev in vim_vm.config.hardware.device if isinstance(dev, vim.vm.device.VirtualDisk)]) # Assert that the UUID assigned to the ephemeral and persistent disks # matches their ids for disk_id in (disk_id_ephemeral, disk_id_persistent): self.assertTrue(disk_id in disk_uuid_map and disk_id in disk_uuid_map[disk_id]) # Assert that no such assignment is done for link-clone root disk. self.assertFalse(disk_id_root in disk_uuid_map) vm_wrapper.detach_disks(vm_id, [disk_id_persistent]) vm_wrapper.delete(request=vm_wrapper.delete_request()) vm_wrapper.delete_disks([disk_id_persistent], validate=True) def test_place_on_multiple_datastores(self): """ Test placement can actually place vm to datastores without image. """ host_datastores = self.vim_client.get_all_datastores() image_datastore = self._find_configured_datastore_in_host_config() dest_datastore = None for ds in host_datastores: if ds.id != image_datastore.id: dest_datastore = ds break if not dest_datastore: raise SkipTest() # Test only 2 datastores, with one image datastore and another # datastore. self.provision_hosts( datastores=[image_datastore.name, dest_datastore.name], used_for_vms=False) self.client_connections() concurrency = 3 atmoic_lock = threading.Lock() results = {"count": 0} # Only copy image to datastore[0] new_image_id = str(uuid.uuid4()) datastore = Datastore(id=image_datastore.name) src_image = Image("ttylinux", datastore) dst_image = Image(new_image_id, datastore) request = Host.CopyImageRequest(src_image, dst_image) response = self.host_client.copy_image(request) assert_that(response.result, is_(CopyImageResultCode.OK)) def _thread(): self._test_create_vm_with_ephemeral_disks(new_image_id, concurrent=True, new_client=True) with atmoic_lock: results["count"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() # Make sure the new image is copied to both datastores, and clean # them up. for ds in (image_datastore, dest_datastore): image = Image(datastore=Datastore(id=ds.name), id=new_image_id) self._delete_image(image) assert_that(results["count"], is_(concurrency)) def test_place_on_datastore_tag(self): host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) self.assertEqual(res.result, GetConfigResultCode.OK) datastores = res.hostConfig.datastores for datastore in datastores: tag = self._type_to_tag(datastore.type) if not tag: continue vm_wrapper = VmWrapper(self.host_client) # Test place disks with only datastore constraint disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], []) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints) # Test place disks with datastore and datastore tag constraint disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], [tag]) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints, expect=PlaceResultCode.OK) # Test place disks with the wrong datastore tag for other_tag in self._other_tags(tag): disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], [other_tag]) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints, expect=PlaceResultCode.NO_SUCH_RESOURCE) def test_provision_without_datastores(self): """ Test that the host uses all the datastores when it gets provisioned without any datastores specified. """ # provision the host without datastores datastores = self.get_all_datastores() self.provision_hosts(datastores=[], image_ds=datastores[0]) # verify that the host configuration contains all the datastores. req = Host.GetConfigRequest() res = self.create_client().get_host_config(req) self.assertEqual(len(res.hostConfig.datastores), len(self.vim_client.get_all_datastores())) def _manage_disk(self, op, **kwargs): task = op(self.vim_client._content.virtualDiskManager, **kwargs) self.vim_client.wait_for_task(task) def _gen_vd_spec(self): spec = vim.VirtualDiskManager.VirtualDiskSpec() spec.disk_type = str(vim.VirtualDiskManager.VirtualDiskType.thin) spec.adapterType = str( vim.VirtualDiskManager.VirtualDiskAdapterType.lsiLogic) return spec def test_finalize_image(self): """ Integration test for atomic image create """ img_id = "test-create-image" tmp_img_id = "-tmp-" + img_id tmp_image, ds = self._create_test_image(tmp_img_id) tmp_image_path = datastore_path(ds.name, "image_" + tmp_img_id) src_vmdk = vmdk_path(ds.id, tmp_img_id, IMAGE_FOLDER_NAME_PREFIX) dst_vmdk = "%s/%s.vmdk" % (tmp_image_path, img_id) try: self._manage_disk(vim.VirtualDiskManager.MoveVirtualDisk_Task, sourceName=src_vmdk, destName=dst_vmdk, force=True) except: logger.error("Error moving vmdk %s" % src_vmdk, exc_info=True) self._manage_disk(vim.VirtualDiskManager.DeleteVirtualDisk_Task, name=src_vmdk) raise dst_image = Image(img_id, ds) req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.OK) request = Host.GetImagesRequest(ds.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item(img_id)) # Issue another create call and it should fail as the source doesn't # exist. req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.IMAGE_NOT_FOUND) # Verify that we fail if the destination already exists. tmp_image, ds = self._create_test_image(tmp_img_id) req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.DESTINATION_ALREADY_EXIST) # cleanup self._delete_image(dst_image) def test_start_image_scanner(self): """ Test image scanner. Make sure the idle images are reported correctly. """ datastore = self._find_configured_datastore_in_host_config() image_id_1 = new_id() dst_image_1, _ = self._create_test_image(image_id_1) image_id_2 = new_id() dst_image_2, _ = self._create_test_image(image_id_2) disk_image_2 = DiskImage(image_id_2, CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=disk_image_2, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=2, flavor_info=self.DEFAULT_DISK_FLAVOR) ] vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_wrapper.create(request=request) logger.info("Image scan, Vm id: %s" % vm_wrapper.id) # Start image scanner start_scan_request = StartImageScanRequest() start_scan_request.datastore_id = datastore.id start_scan_request.scan_rate = 600 start_scan_request.timeout = 30 start_scan_response = self.host_client.start_image_scan( start_scan_request) self.assertEqual(start_scan_response.result, StartImageOperationResultCode.OK) self._get_and_check_inactive_images(datastore.id, image_id_1, True) get_inactive_images_response = self._get_and_check_inactive_images( datastore.id, image_id_2, False) # Start image sweeper start_sweep_request = StartImageSweepRequest() start_sweep_request.datastore_id = datastore.id start_sweep_request.image_descs = get_inactive_images_response.image_descs start_sweep_request.sweep_rate = 600 start_sweep_request.timeout = 30 start_sweep_request.grace_period = 0 start_sweep_response = self.host_client.start_image_sweep( start_sweep_request) self.assertEqual(start_sweep_response.result, StartImageOperationResultCode.OK) self._get_and_check_deleted_images(datastore.id, image_id_1, True) # cleanup vm_wrapper.delete() self._delete_image(dst_image_1, DeleteDirectoryResultCode.DIRECTORY_NOT_FOUND) self._delete_image(dst_image_2) def _get_and_check_inactive_images(self, datastore_id, image_id, found): get_inactive_images_request = GetInactiveImagesRequest() get_inactive_images_request.datastore_id = datastore_id for counter in range(1, 30): time.sleep(1) get_inactive_images_response = self.host_client.get_inactive_images( get_inactive_images_request) if get_inactive_images_response.result is GetMonitoredImagesResultCode.OK: break self.assertEqual(get_inactive_images_response.result, GetMonitoredImagesResultCode.OK) image_descriptors = get_inactive_images_response.image_descs image_found = False logger.info("Image Descriptors: %s" % image_descriptors) logger.info("Target Image Id: %s" % image_id) for image_descriptor in image_descriptors: if image_descriptor.image_id == image_id: image_found = True self.assertEqual(image_found, found) return get_inactive_images_response def _get_and_check_deleted_images(self, datastore_id, image_id, found): get_deleted_images_request = GetInactiveImagesRequest() get_deleted_images_request.datastore_id = datastore_id for counter in range(1, 30): time.sleep(1) get_inactive_deleted_response = self.host_client.get_deleted_images( get_deleted_images_request) if get_inactive_deleted_response.result is GetMonitoredImagesResultCode.OK: break self.assertEqual(get_inactive_deleted_response.result, GetMonitoredImagesResultCode.OK) image_descriptors = get_inactive_deleted_response.image_descs image_found = False logger.info("Image Descriptors: %s" % image_descriptors) logger.info("Target Image Id: %s" % image_id) for image_descriptor in image_descriptors: if image_descriptor.image_id == image_id: image_found = True self.assertEqual(image_found, found) return get_inactive_deleted_response def _type_to_tag(self, type): type_to_tag = { DatastoreType.NFS_3: NFS_TAG, DatastoreType.NFS_41: NFS_TAG, DatastoreType.SHARED_VMFS: SHARED_VMFS_TAG, DatastoreType.LOCAL_VMFS: LOCAL_VMFS_TAG, DatastoreType.LOCAL_VMFS: VSAN_TAG, } if type in type_to_tag: return type_to_tag[type] else: return None def _other_tags(self, tag): tags = [NFS_TAG, SHARED_VMFS_TAG, LOCAL_VMFS_TAG, VSAN_TAG] tags.remove(tag) return tags def _create_constraints(self, datastores, tags): constraints = [] for datastore in datastores: constraints.append( ResourceConstraint(type=ResourceConstraintType.DATASTORE, values=[datastore])) for tag in tags: constraints.append( ResourceConstraint(type=ResourceConstraintType.DATASTORE_TAG, values=[tag])) return constraints def test_create_image_from_vm(self): """ Integration test for creating an image from a VM """ img_id = "test-new-im-from-vm-%s" % new_id() tmp_img_id = "-tmp-" + img_id tmp_image, ds = self._create_test_image(tmp_img_id) tmp_image_path = datastore_path(ds.id, "image_" + tmp_img_id) src_vmdk = vmdk_path(ds.id, tmp_img_id, IMAGE_FOLDER_NAME_PREFIX) vm_wrapper = VmWrapper(self.host_client) try: self._manage_disk(vim.VirtualDiskManager.DeleteVirtualDisk_Task, name=src_vmdk) except: logger.error("Error deleting vmdk when setting up tmp image %s" % src_vmdk, exc_info=True) raise dst_image = Image(img_id, ds) image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=2, flavor_info=self.DEFAULT_DISK_FLAVOR) ] reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_wrapper.create(request=request) # VM in wrong state vm_wrapper.power(Host.PowerVmOp.ON, Host.PowerVmOpResultCode.OK) time.sleep(10) vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.INVALID_VM_POWER_STATE) vm_wrapper.power(Host.PowerVmOp.OFF, Host.PowerVmOpResultCode.OK) time.sleep(10) # Happy case vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.OK) request = Host.GetImagesRequest(ds.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item(img_id)) # Issue another create call and it should fail as the source doesn't # exist. req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.IMAGE_NOT_FOUND) # Verify that we fail if the destination already exists. tmp_image, ds = self._create_test_image(tmp_img_id) vm_wrapper.create_image_from_vm( image_id=tmp_img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.IMAGE_ALREADY_EXIST) vm_wrapper.delete() # VM to create image from is gone. vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.VM_NOT_FOUND) # Create a VM using the new image created vm_wrapper2 = VmWrapper(self.host_client) image = DiskImage(img_id, CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), ] reservation = vm_wrapper2.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper2.create_request(res_id=reservation) vm_wrapper2.create(request=request) vm_wrapper2.power(Host.PowerVmOp.ON, Host.PowerVmOpResultCode.OK) vm_wrapper2.power(Host.PowerVmOp.OFF, Host.PowerVmOpResultCode.OK) vm_wrapper2.delete() # cleanup self._delete_image(dst_image) def test_delete_tmp_image(self): """ Integration test for deleting temp image directory """ img_id = "test-delete-tmp-image" tmp_iamge, ds = self._create_test_image(img_id) tmp_image_path = "image_" + img_id req = DeleteDirectoryRequest(datastore=ds.id, directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.OK) req = DeleteDirectoryRequest(datastore=ds.id, directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.DIRECTORY_NOT_FOUND) req = DeleteDirectoryRequest(datastore="foo_bar", directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.DATASTORE_NOT_FOUND) def _get_agent_id(self): host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) return res.hostConfig.agent_id