public boolean createFullClone(String cloneName, ManagedObjectReference morFolder, ManagedObjectReference morResourcePool, ManagedObjectReference morDs) throws Exception { VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); VirtualMachineRelocateSpec relocSpec = new VirtualMachineRelocateSpec(); cloneSpec.setLocation(relocSpec); cloneSpec.setPowerOn(false); cloneSpec.setTemplate(false); relocSpec.setDatastore(morDs); relocSpec.setPool(morResourcePool); ManagedObjectReference morTask = _context.getService().cloneVMTask(_mor, morFolder, cloneName, cloneSpec); boolean result = _context.getVimClient().waitForTask(morTask); if (result) { _context.waitForTaskProgressDone(morTask); return true; } else { s_logger.error("VMware cloneVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask)); } return false; }
public FakeVirtualMachine addVM(String name, boolean isRunning, VirtualMachineCloneSpec spec){ final FakeVirtualMachine vm = new FakeVirtualMachine(name, name.contains("template"), isRunning); putVM(name, vm); if (spec != null && spec.getLocation()!= null && VirtualMachineRelocateDiskMoveOptions.createNewChildDiskBacking.name().equals(spec.getLocation().getDiskMoveType())){ //((FakeVirtualMachine)vm).set } if (spec != null && spec.getConfig() != null) { final OptionValue[] extraConfig = spec.getConfig().getExtraConfig(); if (extraConfig != null) { for (OptionValue optionValue : extraConfig) { vm.addCustomParam(optionValue.getKey(), String.valueOf(optionValue.getValue())); } } } return vm; }
private VirtualMachine cloneMaster(VirtualMachine master, String tag, String name, VirtualMachineCloneSpec cloneSpec, String folderName) { VirtualMachine cloned = null; try { FolderNameToFolderManagedEntity toFolderManagedEntity = new FolderNameToFolderManagedEntity(serviceInstance, master); Folder folder = toFolderManagedEntity.apply(folderName); Task task = master.cloneVM_Task(folder, name, cloneSpec); String result = task.waitForTask(); if (result.equals(Task.SUCCESS)) { logger.trace("<< after clone search for VM with name: " + name); Retryer<VirtualMachine> retryer = RetryerBuilder.<VirtualMachine>newBuilder() .retryIfResult(Predicates.<VirtualMachine>isNull()) .withStopStrategy(StopStrategies.stopAfterAttempt(5)) .retryIfException().withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)) .build(); cloned = retryer.call(new GetVirtualMachineCallable(name, folder, serviceInstance.get().getInstance().getRootFolder())); } else { String errorMessage = task.getTaskInfo().getError().getLocalizedMessage(); logger.error(errorMessage); } } catch (Exception e) { if (e instanceof NoPermission){ NoPermission noPermission = (NoPermission)e; logger.error("NoPermission: " + noPermission.getPrivilegeId()); } logger.error("Can't clone vm: " + e.toString(), e); propagate(e); } if (cloned == null) logger.error("<< Failed to get cloned VM. " + name); return checkNotNull(cloned, "cloned"); }
private VirtualMachineCloneSpec configureVirtualMachineCloneSpec(VirtualMachineRelocateSpec rSpec, String linuxName, boolean postConfiguration) throws Exception { VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setPowerOn(true); cloneSpec.setTemplate(false); //cloneSpec.setSnapshot(currentSnapshot.getMOR()); cloneSpec.setLocation(rSpec); if (postConfiguration) { CustomizationSpec customizationSpec = new CustomizationSpec(); CustomizationLinuxPrep linuxPrep = new CustomizationLinuxPrep(); CustomizationFixedName fixedName = new CustomizationFixedName(); fixedName.setName(linuxName); linuxPrep.setHostName(fixedName); linuxPrep.setDomain(""); linuxPrep.setHwClockUTC(true); //linuxPrep.setTimeZone("Etc/UTC"); customizationSpec.setIdentity(linuxPrep); customizationSpec.setGlobalIPSettings(new CustomizationGlobalIPSettings()); CustomizationAdapterMapping[] nicSettingMap = new CustomizationAdapterMapping[1]; nicSettingMap[0] = new CustomizationAdapterMapping(); nicSettingMap[0].adapter = new CustomizationIPSettings(); nicSettingMap[0].adapter.setIp(new CustomizationDhcpIpGenerator()); customizationSpec.setNicSettingMap(nicSettingMap); cloneSpec.setCustomization(customizationSpec); } return cloneSpec; }
protected VirtualMachineCloneSpec createCloneSpec(String computeResourceName, String resourcePoolName, String datastoreName) { VirtualMachineRelocateSpec relocateSpec = new VirtualMachineRelocateSpec(); // ComputeResource ComputeResource computeResource = vmwareClient.search(ComputeResource.class, computeResourceName); if (computeResource == null) { // ComputeResourceが見つからない場合 throw new AutoException("EPROCESS-000503", computeResourceName); } // ResourcePool if (StringUtils.isEmpty(resourcePoolName)) { resourcePoolName = "Resources"; } ResourcePool resourcePool = vmwareClient.search(computeResource, ResourcePool.class, resourcePoolName); if (resourcePool == null) { // ResourcePoolが見つからない場合 throw new AutoException("EPROCESS-000504", resourcePoolName); } relocateSpec.setPool(resourcePool.getMOR()); // Datastore if (StringUtils.isNotEmpty(datastoreName)) { // データストアが指定されている場合 Datastore datastore = vmwareClient.search(Datastore.class, datastoreName); if (datastore == null) { // データストアが見つからない場合 throw new AutoException("EPROCESS-000505", datastoreName); } relocateSpec.setDatastore(datastore.getMOR()); } VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setLocation(relocateSpec); cloneSpec.setPowerOn(false); cloneSpec.setTemplate(false); return cloneSpec; }
/** * Method used to connect to data center and clone a virtual machine identified by the inputs provided. * * @param httpInputs Object that has all the inputs necessary to made a connection to data center * @param vmInputs Object that has all the specific inputs necessary to identify the virtual machine that will be * cloned * @return Map with String as key and value that contains returnCode of the operation, success message with task id * of the execution or failure message and the exception if there is one * @throws Exception */ public Map<String, String> cloneVM(HttpInputs httpInputs, VmInputs vmInputs) throws Exception { ConnectionResources connectionResources = new ConnectionResources(httpInputs, vmInputs); try { ManagedObjectReference vmMor = new MorObjectHandler().getMor(connectionResources, ManagedObjectType.VIRTUAL_MACHINE.getValue(), vmInputs.getVirtualMachineName()); if (vmMor != null) { VmUtils utils = new VmUtils(); ManagedObjectReference folder = utils.getMorFolder(vmInputs.getFolderName(), connectionResources); ManagedObjectReference resourcePool = utils.getMorResourcePool(vmInputs.getCloneResourcePool(), connectionResources); ManagedObjectReference host = utils.getMorHost(vmInputs.getCloneHost(), connectionResources, vmMor); ManagedObjectReference dataStore = utils.getMorDataStore(vmInputs.getCloneDataStore(), connectionResources, vmMor, vmInputs); VirtualMachineRelocateSpec vmRelocateSpec = utils.getVirtualMachineRelocateSpec(resourcePool, host, dataStore, vmInputs); VirtualMachineCloneSpec cloneSpec = new VmConfigSpecs().getCloneSpec(vmInputs, vmRelocateSpec); ManagedObjectReference taskMor = connectionResources.getVimPortType() .cloneVMTask(vmMor, folder, vmInputs.getCloneName(), cloneSpec); return new ResponseHelper(connectionResources, taskMor).getResultsMap("Success: The [" + vmInputs.getVirtualMachineName() + "] VM was successfully cloned. The taskId is: " + taskMor.getValue(), "Failure: The [" + vmInputs.getVirtualMachineName() + "] VM could not be cloned."); } else { return ResponseUtils.getVmNotFoundResultsMap(vmInputs); } } catch (Exception ex) { return ResponseUtils.getResultsMap(ex.toString(), Outputs.RETURN_CODE_FAILURE); } finally { if (httpInputs.isCloseSession()) { connectionResources.getConnection().disconnect(); clearConnectionFromContext(httpInputs.getGlobalSessionObject()); } } }
public ComputeState createInstanceFromSnapshot() throws Exception { String message = ""; if (this.ctx.snapshotMoRef == null) { message = String.format("No MoRef found for the specified snapshot %s", this.ctx.child.documentSelfLink); logger.error(message); this.ctx.fail(new IllegalStateException(message)); } if (this.ctx.referenceComputeMoRef == null) { if (this.ctx.snapshotMoRef == null) { message = String.format("No MoRef found for the reference compute for linkedclone creation for %s.", this.ctx.child.documentSelfLink); logger.error(message); this.ctx.fail(new IllegalStateException(message)); } } VirtualMachineRelocateSpec relocateSpec = new VirtualMachineRelocateSpec(); relocateSpec.setDiskMoveType(VirtualMachineRelocateDiskMoveOptions .CREATE_NEW_CHILD_DISK_BACKING.value()); VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setPowerOn(false); cloneSpec.setLocation(relocateSpec); cloneSpec.setSnapshot(this.ctx.snapshotMoRef); cloneSpec.setTemplate(false); ManagedObjectReference folder = getVmFolder(); String displayName = this.ctx.child.name; ManagedObjectReference linkedCloneTask = getVimPort() .cloneVMTask(this.ctx.referenceComputeMoRef, folder, displayName, cloneSpec); TaskInfo info = waitTaskEnd(linkedCloneTask); if (info.getState() == TaskInfoState.ERROR) { MethodFault fault = info.getError().getFault(); if (fault instanceof FileAlreadyExists) { // a .vmx file already exists, assume someone won the race to create the vm return null; } else { return VimUtils.rethrow(info.getError()); } } ManagedObjectReference clonedVM = (ManagedObjectReference) info.getResult(); if (clonedVM == null) { // vm was created by someone else return null; } // store reference to created vm for further processing this.vm = clonedVM; customizeAfterClone(); ComputeState state = new ComputeState(); state.resourcePoolLink = VimUtils .firstNonNull(this.ctx.child.resourcePoolLink, this.ctx.parent.resourcePoolLink); return state; }
private ManagedObjectReference cloneVm(ManagedObjectReference template) throws Exception { ManagedObjectReference folder = getVmFolder(); List<VirtualMachineDefinedProfileSpec> pbmSpec = getPbmProfileSpec(this.bootDisk); ManagedObjectReference datastore = getDataStoreForDisk(this.bootDisk, pbmSpec); ManagedObjectReference resourcePool = getResourcePool(); Map<String, Object> props = this.get.entityProps(template, VimPath.vm_config_hardware_device); ArrayOfVirtualDevice devices = (ArrayOfVirtualDevice) props .get(VimPath.vm_config_hardware_device); VirtualDisk vd = devices.getVirtualDevice().stream() .filter(d -> d instanceof VirtualDisk) .map(d -> (VirtualDisk) d).findFirst().orElse(null); VirtualMachineRelocateSpec relocSpec = new VirtualMachineRelocateSpec(); relocSpec.setDatastore(datastore); if (pbmSpec != null) { pbmSpec.stream().forEach(spec -> { relocSpec.getProfile().add(spec); }); } relocSpec.setFolder(folder); relocSpec.setPool(resourcePool); relocSpec.setDiskMoveType(computeDiskMoveType().value()); VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setLocation(relocSpec); //Set the provisioning type of the parent disk. VirtualMachineRelocateSpecDiskLocator diskProvisionTypeLocator = setProvisioningType(vd, datastore, pbmSpec); if (diskProvisionTypeLocator != null) { cloneSpec.getLocation().getDisk().add(diskProvisionTypeLocator); } cloneSpec.setPowerOn(false); cloneSpec.setTemplate(false); String displayName = this.ctx.child.name; ManagedObjectReference cloneTask = getVimPort() .cloneVMTask(template, folder, displayName, cloneSpec); TaskInfo info = waitTaskEnd(cloneTask); if (info.getState() == TaskInfoState.ERROR) { MethodFault fault = info.getError().getFault(); if (fault instanceof FileAlreadyExists) { // a .vmx file already exists, assume someone won the race to create the vm return null; } else { return VimUtils.rethrow(info.getError()); } } return (ManagedObjectReference) info.getResult(); }
@Override public NodeAndInitialCredentials<VirtualMachine> createNodeWithGroupEncodedIntoName(String tag, String name, Template template) { VSphereTemplateOptions vOptions = VSphereTemplateOptions.class.cast(template.getOptions()); String datacenterName = vOptions.datacenterName(); try (VSphereServiceInstance instance = this.serviceInstance.get(); VSphereHost sphereHost = hostFunction.apply(datacenterName); /*VSphereHost sphereHost = vSphereHost.get();*/) { Folder rootFolder = instance.getInstance().getRootFolder(); ComputerNameValidator.INSTANCE.validate(name); VirtualMachine master = getVMwareTemplate(template.getImage().getId(), rootFolder); ResourcePool resourcePool = checkNotNull(tryFindResourcePool(rootFolder, sphereHost.getHost().getName()).orNull(), "resourcePool"); logger.trace("<< trying to use ResourcePool: " + resourcePool.getName()); // VSphereTemplateOptions vOptions = VSphereTemplateOptions.class.cast(template.getOptions()); VirtualMachineCloneSpec cloneSpec = new MasterToVirtualMachineCloneSpec(resourcePool, sphereHost.getDatastore(), VSphereApiMetadata.defaultProperties().getProperty(CLONING), name, vOptions.postConfiguration()).apply(master); Set<String> networks = vOptions.getNetworks(); VirtualMachineConfigSpec virtualMachineConfigSpec = new VirtualMachineConfigSpec(); virtualMachineConfigSpec.setMemoryMB((long) template.getHardware().getRam()); if (template.getHardware().getProcessors().size() > 0) virtualMachineConfigSpec.setNumCPUs((int) template.getHardware().getProcessors().get(0).getCores()); else virtualMachineConfigSpec.setNumCPUs(1); Set<NetworkConfig> networkConfigs = Sets.newHashSet(); for (String network : networks) { NetworkConfig config = networkConfigurationForNetworkAndOptions.apply(network, vOptions); networkConfigs.add(config); } List<VirtualDeviceConfigSpec> updates = configureVmHardware(name, template, master, vOptions, networkConfigs); virtualMachineConfigSpec.setDeviceChange(updates.toArray(new VirtualDeviceConfigSpec[updates.size()])); cloneSpec.setConfig(virtualMachineConfigSpec); vOptions.getPublicKey(); VirtualMachine cloned = null; try { cloned = cloneMaster(master, tag, name, cloneSpec, vOptions.vmFolder()); Set<String> tagsFromOption = vOptions.getTags(); if (tagsFromOption.size() > 0) { String tags = Joiner.on(",").join(vOptions.getTags()); cloned.getServerConnection().getServiceInstance().getCustomFieldsManager().setField(cloned, customFields.get().get(VSphereConstants.JCLOUDS_TAGS).getKey(), tags); cloned.getServerConnection().getServiceInstance().getCustomFieldsManager().setField(cloned, customFields.get().get(VSphereConstants.JCLOUDS_GROUP).getKey(), tag); if (vOptions.postConfiguration()) postConfiguration(cloned, name, tag, networkConfigs); else { VSpherePredicate.WAIT_FOR_VMTOOLS(1000 * 60 * 60 * 2, TimeUnit.MILLISECONDS).apply(cloned); } } } catch (Exception e) { logger.error("Can't clone vm " + master.getName() + ", Error message: " + e.toString(), e); propagate(e); } checkAndRecoverNicConfiguration(serviceInstance.get(), cloned); NodeAndInitialCredentials<VirtualMachine> nodeAndInitialCredentials = new NodeAndInitialCredentials<VirtualMachine>(cloned, cloned.getName(), LoginCredentials.builder().user("root") .password(vmInitPassword) .build()); return nodeAndInitialCredentials; } catch (Throwable t) { logger.error("Got ERROR while create new VM : " + t.toString()); Throwables.propagateIfPossible(t); } return null; }
@Override public VirtualMachineCloneSpec apply(VirtualMachine master) { return prepareCloneSpec(master, resourcePool, datastore, linuxName, postConfiguration); }
public static void main(String[] args) throws Exception { CommandLineParser clp = new CommandLineParser(constructOptions(), args); String urlStr = clp.get_option("url"); String username = clp.get_option("username"); String password = clp.get_option("password"); String cloneName = clp.get_option("CloneName"); String vmPath = clp.get_option("vmPath"); String datacenterName= clp.get_option("DatacenterName"); try { ServiceInstance si = new ServiceInstance(new URL(urlStr), username, password, true); VirtualMachine vm = (VirtualMachine) si.getSearchIndex().findByInventoryPath(vmPath); Datacenter dc = (Datacenter) si.getSearchIndex().findByInventoryPath(datacenterName); if(vm==null || dc ==null) { System.out.println("VirtualMachine or Datacenter path is NOT correct. Pls double check. "); return; } Folder vmFolder = dc.getVmFolder(); VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setLocation(new VirtualMachineRelocateSpec()); cloneSpec.setPowerOn(false); cloneSpec.setTemplate(false); Task task = vm.cloneVM_Task(vmFolder, cloneName, cloneSpec); System.out.println("Launching the VM clone task. It might take a while. Please wait for the result ..."); String status = task.waitForMe(); if(status==Task.SUCCESS) { System.out.println("Virtual Machine got cloned successfully."); } else { System.out.println("Failure -: Virtual Machine cannot be cloned"); } } catch(RemoteException re) { re.printStackTrace(); } catch(MalformedURLException mue) { mue.printStackTrace(); } }
public static void main(String[] args) throws Exception { if(args.length!=5) { System.out.println("Usage: java CloneVM <url> " + "<username> <password> <vmname> <clonename>"); System.exit(0); } String vmname = args[3]; String cloneName = args[4]; ServiceInstance si = new ServiceInstance( new URL(args[0]), args[1], args[2], true); Folder rootFolder = si.getRootFolder(); VirtualMachine vm = (VirtualMachine) new InventoryNavigator( rootFolder).searchManagedEntity( "VirtualMachine", vmname); if(vm==null) { System.out.println("No VM " + vmname + " found"); si.getServerConnection().logout(); return; } VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setLocation(new VirtualMachineRelocateSpec()); cloneSpec.setPowerOn(false); cloneSpec.setTemplate(false); Task task = vm.cloneVM_Task((Folder) vm.getParent(), cloneName, cloneSpec); System.out.println("Launching the VM clone task. " + "Please wait ..."); String status = task.waitForMe(); if(status==Task.SUCCESS) { System.out.println("VM got cloned successfully."); } else { System.out.println("Failure -: VM cannot be cloned"); } }
@Override public String createMachine( TargetHandlerParameters parameters ) throws TargetException { this.logger.fine( "Creating a new VM @ VMware." ); // For IaaS, we only expect root instance names to be passed if( InstanceHelpers.countInstances( parameters.getScopedInstancePath()) > 1 ) throw new TargetException( "Only root instances can be passed in arguments." ); String rootInstanceName = InstanceHelpers.findRootInstancePath( parameters.getScopedInstancePath()); // Deal with the creation try { System.setProperty("org.xml.sax.driver","org.apache.xerces.parsers.SAXParser"); Map<String,String> targetProperties = parameters.getTargetProperties(); final String machineImageId = targetProperties.get( TEMPLATE ); final ServiceInstance vmwareServiceInstance = getServiceInstance( targetProperties ); final ComputeResource vmwareComputeResource = (ComputeResource)( new InventoryNavigator( vmwareServiceInstance.getRootFolder()) .searchManagedEntity("ComputeResource", targetProperties.get( CLUSTER ))); // Generate the user data first, so that nothing has been done on the IaaS if it fails String userData = UserDataHelpers.writeUserDataAsString( parameters.getMessagingProperties(), parameters.getDomain(), parameters.getApplicationName(), rootInstanceName ); VirtualMachine vm = getVirtualMachine( vmwareServiceInstance, machineImageId ); String vmwareDataCenter = targetProperties.get( DATA_CENTER ); Folder vmFolder = ((Datacenter)(new InventoryNavigator( vmwareServiceInstance.getRootFolder()) .searchManagedEntity("Datacenter", vmwareDataCenter))) .getVmFolder(); this.logger.fine("machineImageId=" + machineImageId); if (vm == null || vmFolder == null) throw new TargetException("VirtualMachine (= " + vm + " ) or Datacenter path (= " + vmFolder + " ) is NOT correct. Please, double check."); VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setLocation(new VirtualMachineRelocateSpec()); cloneSpec.setPowerOn(false); cloneSpec.setTemplate(true); VirtualMachineConfigSpec vmSpec = new VirtualMachineConfigSpec(); vmSpec.setAnnotation( userData ); cloneSpec.setConfig(vmSpec); Task task = vm.cloneVM_Task( vmFolder, rootInstanceName, cloneSpec ); this.logger.fine("Cloning the template: "+ machineImageId +" ..."); String status = task.waitForTask(); if (!status.equals(Task.SUCCESS)) throw new TargetException("Failure: Virtual Machine cannot be cloned." ); VirtualMachine vm2 = getVirtualMachine( vmwareServiceInstance, rootInstanceName ); this.logger.fine("Transforming the clone template to Virtual machine ..."); vm2.markAsVirtualMachine( vmwareComputeResource.getResourcePool(), null); DynamicProperty dprop = new DynamicProperty(); dprop.setName("guestinfo.userdata"); dprop.setVal(userData); vm2.getGuest().setDynamicProperty(new DynamicProperty[]{dprop}); task = vm2.powerOnVM_Task(null); this.logger.fine("Starting the virtual machine: "+ rootInstanceName +" ..."); status = task.waitForTask(); if( ! status.equals( Task.SUCCESS )) throw new TargetException("Failure: Virtual Machine cannot be started." ); return vm2.getName(); } catch( Exception e ) { throw new TargetException( e ); } }
public boolean createLinkedClone(String cloneName, ManagedObjectReference morBaseSnapshot, ManagedObjectReference morFolder, ManagedObjectReference morResourcePool, ManagedObjectReference morDs) throws Exception { assert (morBaseSnapshot != null); assert (morFolder != null); assert (morResourcePool != null); assert (morDs != null); VirtualDisk[] independentDisks = getAllIndependentDiskDevice(); VirtualMachineRelocateSpec rSpec = new VirtualMachineRelocateSpec(); if (independentDisks.length > 0) { List<VirtualMachineRelocateSpecDiskLocator> diskLocator = new ArrayList<VirtualMachineRelocateSpecDiskLocator>(independentDisks.length); for (int i = 0; i < independentDisks.length; i++) { VirtualMachineRelocateSpecDiskLocator loc = new VirtualMachineRelocateSpecDiskLocator(); loc.setDatastore(morDs); loc.setDiskId(independentDisks[i].getKey()); loc.setDiskMoveType(VirtualMachineRelocateDiskMoveOptions.MOVE_ALL_DISK_BACKINGS_AND_DISALLOW_SHARING.value()); diskLocator.add(loc); } rSpec.setDiskMoveType(VirtualMachineRelocateDiskMoveOptions.CREATE_NEW_CHILD_DISK_BACKING.value()); rSpec.getDisk().addAll(diskLocator); } else { rSpec.setDiskMoveType(VirtualMachineRelocateDiskMoveOptions.CREATE_NEW_CHILD_DISK_BACKING.value()); } rSpec.setPool(morResourcePool); VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec(); cloneSpec.setPowerOn(false); cloneSpec.setTemplate(false); cloneSpec.setLocation(rSpec); cloneSpec.setSnapshot(morBaseSnapshot); ManagedObjectReference morTask = _context.getService().cloneVMTask(_mor, morFolder, cloneName, cloneSpec); boolean result = _context.getVimClient().waitForTask(morTask); if (result) { _context.waitForTaskProgressDone(morTask); return true; } else { s_logger.error("VMware cloneVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask)); } return false; }