Advertisement
Guest User

Untitled

a guest
Jun 27th, 2016
192
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.77 KB | None | 0 0
  1. /*
  2. * ****************************************************************************
  3. * Copyright VMware, Inc. 2010-2016. All Rights Reserved.
  4. * ****************************************************************************
  5. *
  6. * This software is made available for use under the terms of the BSD
  7. * 3-Clause license:
  8. *
  9. * Redistribution and use in source and binary forms, with or without
  10. * modification, are permitted provided that the following conditions
  11. * are met:
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. Neither the name of the copyright holder nor the names of its
  21. * contributors may be used to endorse or promote products derived
  22. * from this software without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28. * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  30. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  31. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  32. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  33. * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  34. * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  35. */
  36.  
  37.  
  38. package com.vmware.vm;
  39.  
  40. import com.vmware.vim25.*;
  41. import com.vmware.common.annotations.Action;
  42. import com.vmware.common.annotations.Option;
  43. import com.vmware.common.annotations.Sample;
  44. import com.vmware.connection.BasicConnection;
  45. import com.vmware.connection.ConnectedVimServiceBase;
  46. import com.vmware.connection.Connection;
  47. import com.vmware.connection.helpers.GetMOREF;
  48. import java.util.*;
  49. import javax.xml.ws.soap.SOAPFaultException;
  50.  
  51. /**
  52. * <pre>
  53. * XVCvMotion
  54. *
  55. * Used to Relocate VM from one VC to another.
  56. *
  57. * <b>Parameters:</b>
  58. * url [required] : url of the web service
  59. * username [required] : username for the authentication
  60. * password [required] : password for the authentication
  61. * vmname [required] : name of the virtual machine
  62. * destcluster [required] : name of the cluster on target virtual center where to migrate the virtual machine.
  63. * remoteurl [required] : url of web service for target virtual center
  64. * ruser [required] : username for the authentication to the target virtual center.
  65. * rpassword [required] : password for the authentication to the target virtual center.
  66. * rthumbprint [required] : thumbprint of the target virtual center.
  67. * targetfolder [optional] : folder on the target virtual center where to migrate the virtual machine.
  68. *
  69. * <b>Command Line:</b>
  70. * run.bat com.vmware.vm.XVCvMotion --url [URLString] --username [User] --password [Password]
  71. * --vmname [VMName] --destcluster [Target Cluster] --remoteurl [TargetVC URLString] --ruser [TargetVC User]
  72. * --rpassword [TargetVC Password] --rthumbprint [TargetVC Thumbprint] --targetfolder [Target Folder]
  73. * </pre>
  74. */
  75.  
  76. @Sample(name = "XVCvMotion", description = "Used to Relocate VM from one VC to another")
  77. public class XVCvMotion extends ConnectedVimServiceBase {
  78.  
  79. /*
  80. * Connection input parameters for the Source and target vCenter Servers.
  81. */
  82. String vmName = null;
  83. String destCluster = null;
  84. String remoteurl = null;
  85. String ruser = null;
  86. String rpassword = null;
  87. String rthumbprint = null;
  88. String targetFolder = null; // default value for targetFolder is vm
  89. private static VimPortType destVimPort = null; // VimPort for the target VC
  90. // Connection
  91. private static ServiceContent destServiceContent = null;
  92.  
  93. @Option(name = "vmname", description = "name of the virtual machine to be migrated")
  94. public void setVmName(String name) {
  95. this.vmName = name;
  96. }
  97.  
  98. @Option(name = "clustername", description = "Target Cluster Name")
  99. public void setCluster(String clusterName) {
  100. this.destCluster = clusterName;
  101. }
  102.  
  103. @Option(name = "remoteurl", description = "Target vCenter URL ")
  104. public void setRemoteUrl(String remoteurl) {
  105. this.remoteurl = remoteurl;
  106. }
  107.  
  108. @Option(name = "ruser", description = "Target vCenter username")
  109. public void setRuser(String ruser) {
  110. this.ruser = ruser;
  111. }
  112.  
  113. @Option(name = "rpassword", description = "Target vCenter Password")
  114. public void setRpassword(String rpassword) {
  115. this.rpassword = rpassword;
  116. }
  117.  
  118. @Option(name = "rthumbprint", description = "Thumbprint of Target vCenter")
  119. public void setThumbprint(String thumbprint) {
  120. this.rthumbprint = thumbprint;
  121. }
  122.  
  123. @Option(name = "targetfolder", description = "Target Folder", required = false)
  124. public void setTargetFolder(String targetFolder) {
  125. this.targetFolder = targetFolder;
  126. }
  127.  
  128. /**
  129. * This method returns Managed Object Reference to VM root Folder
  130. *
  131. * @param MOR
  132. * for the starting Point of Filter
  133. * @param Type
  134. * OF Entity we are looking For.
  135. * @return Managed Object Reference to VM root Folder
  136. * @throws RuntimeFaultFaultMsg
  137. * @throws InvalidPropertyFaultMsg
  138. */
  139. ManagedObjectReference getVMFolderMor(ManagedObjectReference mor,
  140. String type, String name) throws RuntimeFaultFaultMsg,
  141. InvalidPropertyFaultMsg {
  142.  
  143. List<SelectionSpec> tSpec = buildVmFolderTraversal();
  144. ManagedObjectReference retVal = null;
  145.  
  146. // Create PropertySpec
  147. PropertySpec propertySpec = new PropertySpec();
  148. propertySpec.setAll(Boolean.FALSE);
  149. propertySpec.getPathSet().add("name");
  150. propertySpec.setType(type);
  151.  
  152. // Now create ObjectSpec
  153. ObjectSpec objectSpec = new ObjectSpec();
  154. objectSpec.setObj(mor);
  155. objectSpec.setSkip(Boolean.TRUE);
  156. objectSpec.getSelectSet().addAll(tSpec);
  157.  
  158. // Create PropertyFilterSpec using the PropertySpec and ObjectSpec
  159. PropertyFilterSpec propertyFilterSpec = new PropertyFilterSpec();
  160. propertyFilterSpec.getPropSet().add(propertySpec);
  161. propertyFilterSpec.getObjectSet().add(objectSpec);
  162.  
  163. List<PropertyFilterSpec> listpfs = new ArrayList<PropertyFilterSpec>(1);
  164. listpfs.add(propertyFilterSpec);
  165.  
  166. List<ObjectContent> listobjcont = retrievePropertiesAllObjects(listpfs);
  167. if (listobjcont != null) {
  168. for (ObjectContent oc : listobjcont) {
  169. ManagedObjectReference mr = oc.getObj();
  170. String entityName = null;
  171. List<DynamicProperty> listDynamicProps = oc.getPropSet();
  172. DynamicProperty[] dps = listDynamicProps
  173. .toArray(new DynamicProperty[listDynamicProps.size()]);
  174. if (dps != null) {
  175. for (DynamicProperty dp : dps) {
  176. entityName = (String) dp.getVal();
  177. }
  178. }
  179. if (entityName != null && entityName.equals(name)) {
  180. retVal = mr;
  181. break;
  182. }
  183. }
  184. } else {
  185. System.out.println("The Object Content is Null");
  186. }
  187. return retVal;
  188. }
  189.  
  190. // RetrievePropertiesAllObjects
  191.  
  192. List<ObjectContent> retrievePropertiesAllObjects(
  193. List<PropertyFilterSpec> listpfs) throws RuntimeFaultFaultMsg,
  194. InvalidPropertyFaultMsg {
  195. RetrieveOptions propObjectRetrieveOpts = new RetrieveOptions();
  196. List<ObjectContent> listobjcontent = new ArrayList<ObjectContent>();
  197. RetrieveResult rslts = destVimPort.retrievePropertiesEx(
  198. destServiceContent.getPropertyCollector(), listpfs,
  199. propObjectRetrieveOpts);
  200. if (rslts != null && rslts.getObjects() != null
  201. && !rslts.getObjects().isEmpty()) {
  202. listobjcontent.addAll(rslts.getObjects());
  203. }
  204. String token = null;
  205. if (rslts != null && rslts.getToken() != null) {
  206. token = rslts.getToken();
  207. }
  208.  
  209. while (token != null && !token.isEmpty()) {
  210. rslts = destVimPort.continueRetrievePropertiesEx(destServiceContent
  211. .getPropertyCollector(), token);
  212. token = null;
  213. if (rslts != null) {
  214. token = rslts.getToken();
  215. if (rslts.getObjects() != null && !rslts.getObjects().isEmpty()) {
  216. listobjcontent.addAll(rslts.getObjects());
  217. }
  218. }
  219. }
  220. return listobjcontent;
  221. }
  222.  
  223. // buildVmFolderTraversal
  224. // This to Get the MOR for the Folder where to place the VM.
  225. //It Include traversal from CLuster to datacenter and then to VMFolder.
  226.  
  227. List<SelectionSpec> buildVmFolderTraversal() {
  228.  
  229. // DC -> VM Folder
  230. TraversalSpec dcToVmf = new TraversalSpec();
  231. dcToVmf.setType("Datacenter");
  232. dcToVmf.setSkip(Boolean.FALSE);
  233. dcToVmf.setPath("vmFolder");
  234. dcToVmf.setName("dcToVmf");
  235. dcToVmf.getSelectSet().add(getSelectionSpec("visitFolders"));
  236.  
  237. // CLUSTER -> Parent
  238. TraversalSpec CrtoPr = new TraversalSpec();
  239. CrtoPr.setType("ClusterComputeResource");
  240. CrtoPr.setSkip(Boolean.FALSE);
  241. CrtoPr.setPath("parent");
  242. CrtoPr.setName("cctoparent");
  243. CrtoPr.getSelectSet().add(getSelectionSpec("foldertoparent"));
  244.  
  245. // Folder->Parent
  246. TraversalSpec FoltoPr = new TraversalSpec();
  247. FoltoPr.setType("Folder");
  248. FoltoPr.setSkip(Boolean.FALSE);
  249. FoltoPr.setPath("parent");
  250. FoltoPr.setName("foldertoparent");
  251. FoltoPr.getSelectSet().add(getSelectionSpec("foldertoparent"));
  252. FoltoPr.getSelectSet().add(getSelectionSpec("dcToVmf"));
  253.  
  254. // For Folder -> Folder recursion
  255. TraversalSpec visitFolders = new TraversalSpec();
  256. visitFolders.setType("Folder");
  257. visitFolders.setPath("childEntity");
  258. visitFolders.setSkip(Boolean.FALSE);
  259. visitFolders.setName("visitFolders");
  260. visitFolders.getSelectSet().add(getSelectionSpec("visitFolders"));
  261.  
  262.  
  263. List<SelectionSpec> resultspec = new ArrayList<SelectionSpec>();
  264. resultspec.add(visitFolders);
  265. resultspec.add(dcToVmf);
  266. resultspec.add(CrtoPr);
  267. resultspec.add(FoltoPr);
  268. return resultspec;
  269. }
  270.  
  271. // GetSelectionSpec
  272. SelectionSpec getSelectionSpec(String name) {
  273. SelectionSpec genericSpec = new SelectionSpec();
  274. genericSpec.setName(name);
  275. return genericSpec;
  276. }
  277.  
  278. /**
  279. * This method returns a boolean value specifying whether the Task is
  280. * succeeded or failed.
  281. *
  282. * @param task
  283. * ManagedObjectReference representing the Task.
  284. * @return boolean value representing the Task result.
  285. * @throws InvalidCollectorVersionFaultMsg
  286. *
  287. * @throws RuntimeFaultFaultMsg
  288. * @throws InvalidPropertyFaultMsg
  289. */
  290. boolean getTaskResultAfterDone(ManagedObjectReference task)
  291. throws InvalidPropertyFaultMsg, RuntimeFaultFaultMsg,
  292. InvalidCollectorVersionFaultMsg {
  293.  
  294. boolean retVal = false;
  295.  
  296. // info has a property - state for state of the task
  297. Object[] result = waitForValues.wait(task, new String[] { "info.state",
  298. "info.error" }, new String[] { "state" },
  299. new Object[][] { new Object[] { TaskInfoState.SUCCESS,
  300. TaskInfoState.ERROR } });
  301.  
  302. if (result[0].equals(TaskInfoState.SUCCESS)) {
  303. retVal = true;
  304. }
  305. if (result[1] instanceof LocalizedMethodFault) {
  306. throw new RuntimeException(((LocalizedMethodFault) result[1])
  307. .getLocalizedMessage());
  308. }
  309. return retVal;
  310. }
  311.  
  312. @Action
  313. public void placeVM() throws DuplicateNameFaultMsg, InvalidNameFaultMsg,
  314. RuntimeFaultFaultMsg, InvalidPropertyFaultMsg, FileFaultFaultMsg,
  315. InsufficientResourcesFaultFaultMsg, InvalidDatastoreFaultMsg,
  316. InvalidStateFaultMsg, MigrationFaultFaultMsg, TimedoutFaultMsg,
  317. VmConfigFaultFaultMsg, InvalidCollectorVersionFaultMsg,
  318. InvalidLocaleFaultMsg, InvalidLoginFaultMsg {
  319.  
  320. PlacementResult placementResult;
  321. ManagedObjectReference clusterMor = null;
  322. PlacementSpec placementSpec = new PlacementSpec();
  323. ManagedObjectReference vmMOR = null;
  324. System.out.println("Searching for VM '" + vmName + "'..");
  325. Map<String, ManagedObjectReference> vms = getMOREFs.inContainerByType(
  326. serviceContent.getRootFolder(), "VirtualMachine");
  327. if (vms.containsKey(vmName)) {
  328. System.out.println("Found VM: " + vmName);
  329. vmMOR = vms.get(vmName);
  330. } else {
  331. throw new IllegalStateException("No VM by the name of '" + vmName
  332. + "' found!");
  333. }
  334. Map<String, Object> vmProperties = getMOREFs.entityProps(vmMOR,
  335. new String[] { "config.version", "config.cpuAllocation",
  336. "config.memoryAllocation", "config.hardware.numCPU",
  337. "config.hardware.memoryMB", "config.files",
  338. "config.swapPlacement", "config.hardware.device",
  339. "config.name" });
  340.  
  341. // Setting VirtualMachineConfigSpec properties
  342. System.out.println("Setting VirtualMachineConfigSpec properties--");
  343. VirtualMachineConfigSpec configSpec = new VirtualMachineConfigSpec();
  344. configSpec.setVersion((String) vmProperties.get("config.version"));
  345. configSpec.setCpuAllocation((ResourceAllocationInfo) vmProperties
  346. .get("config.cpuAllocation"));
  347. configSpec.setMemoryAllocation((ResourceAllocationInfo) vmProperties
  348. .get("config.memoryAllocation"));
  349. configSpec.setNumCPUs((Integer) vmProperties
  350. .get("config.hardware.numCPU"));
  351. Integer memoryinMBs = (Integer) vmProperties
  352. .get("config.hardware.memoryMB");
  353. configSpec.setMemoryMB(memoryinMBs.longValue());
  354. configSpec.setFiles((VirtualMachineFileInfo) vmProperties
  355. .get("config.files"));
  356. configSpec.setSwapPlacement((String) vmProperties
  357. .get("config.swapPlacement"));
  358. configSpec.setName((String) vmProperties.get("config.name"));
  359. List<VirtualDevice> listvd = ((ArrayOfVirtualDevice) vmProperties
  360. .get("config.hardware.device")).getVirtualDevice();
  361. for (VirtualDevice device : listvd) {
  362. VirtualDeviceConfigSpec vdConfigSpec = new VirtualDeviceConfigSpec();
  363. vdConfigSpec.setDevice(device);
  364. configSpec.getDeviceChange().add(vdConfigSpec);
  365. }
  366. placementSpec.setConfigSpec(configSpec);
  367.  
  368. // Connection to Destination VC
  369. System.out.println("Connecting to Destination vCenter - " + remoteurl);
  370. DestConnect destConnect = new DestConnect();
  371. Connection targetConnection = destConnect.getDestConnection(remoteurl,
  372. ruser, rpassword);
  373. destVimPort = targetConnection.getVimPort();
  374. destServiceContent = targetConnection.getServiceContent();
  375.  
  376. // clusters will contain the list of Clusters available
  377. System.out.println("Looking for the Cluster defined on Destination VC");
  378. Map<String, ManagedObjectReference> clusters = destConnect.destGetMOREFs
  379. .inContainerByType(destServiceContent.getRootFolder(),
  380. "ClusterComputeResource");
  381. if (clusters.containsKey(destCluster)) {
  382. clusterMor = clusters.get(destCluster);
  383. System.out.println("Found Cluster '" + destCluster
  384. + "'on Destination vCenter!");
  385. } else {
  386. throw new IllegalStateException("No Cluster by the name of '"
  387. + destCluster + "' found!");
  388. }
  389. System.out.println("Getting Recommendations from DRS for XVCvMotion--");
  390. PlacementAction action = new PlacementAction();
  391. try {
  392. placementResult = destVimPort.placeVm(clusterMor, placementSpec);
  393. action = getPlacementAction(placementResult);
  394. } catch (SOAPFaultException e) {
  395. if (e.getMessage().contains("vim.fault.InvalidState")) {
  396. throw new IllegalStateException(
  397. "Check the Cluster setting and make sure that DRS is enabled");
  398. }
  399. else {
  400. throw new IllegalStateException(e.getMessage());
  401. }
  402. }
  403.  
  404. if (action != null) {
  405. ManagedObjectReference vmfoldermor;
  406.  
  407. if (targetFolder == null) {
  408. System.out
  409. .println("Target Folder undefined Using Default VM Folder");
  410. vmfoldermor = getVMFolderMor(clusterMor, "Folder", "vm");
  411. } else {
  412. System.out
  413. .println("Setting Defined TargetFolder as VMFolder for XVCvMotion");
  414. vmfoldermor = getVMFolderMor(clusterMor, "Folder", targetFolder);
  415. }
  416. if (vmfoldermor != null) {
  417.  
  418. ServiceLocatorNamePassword slNamePassowrd = new ServiceLocatorNamePassword();
  419. slNamePassowrd.setPassword(rpassword);
  420. slNamePassowrd.setUsername(ruser);
  421. ServiceLocator locator = new ServiceLocator();
  422. locator.setCredential(slNamePassowrd);
  423. locator.setUrl(remoteurl);
  424. locator.setInstanceUuid(destServiceContent.getAbout()
  425. .getInstanceUuid());
  426. locator.setSslThumbprint(rthumbprint);
  427. VirtualMachineRelocateSpec relocateSpec = action
  428. .getRelocateSpec();
  429. relocateSpec.setService(locator);
  430. // Manually set the folder else Exception would be thrown
  431. relocateSpec.setFolder(vmfoldermor);
  432. System.out.println("Relocation in Progress!");
  433. ManagedObjectReference taskMOR = vimPort.relocateVMTask(vmMOR,
  434. relocateSpec,
  435. VirtualMachineMovePriority.DEFAULT_PRIORITY);
  436. if (getTaskResultAfterDone(taskMOR)) {
  437. System.out.println("Relocation done successfully");
  438. } else {
  439. System.out.println("Error:: Relocation failed");
  440. }
  441. } else {
  442. throw new IllegalStateException("No Folder by the name of '"
  443. + targetFolder + "' found!");
  444. }
  445. } else {
  446. System.out.println("Recommendations are not correct");
  447. }
  448. destConnect.disconnect();
  449. }
  450.  
  451. /**
  452. * This method is to return the first valid PlacementAction out of the DRS
  453. * recommendations.
  454. *
  455. * @param placementResult
  456. * @return PlacementAction
  457. * @throws RuntimeFaultFaultMsg
  458. * @throws InvalidPropertyFaultMsg
  459. */
  460. PlacementAction getPlacementAction(PlacementResult placementResult)
  461. throws RuntimeFaultFaultMsg, InvalidPropertyFaultMsg {
  462. List<ClusterRecommendation> recommendations = placementResult
  463. .getRecommendations();
  464. PlacementAction placementAction = null;
  465. int size = recommendations.size();
  466. boolean actionOk = false;
  467. if (size > 0) {
  468. System.out.println("Total number of recommendations are " + size);
  469. System.out
  470. .println("Processing the xvcvmotion placement recommendations out of the recommendations received");
  471. for (ClusterRecommendation clusterrecommend : recommendations) {
  472. if (clusterrecommend.getReason().equalsIgnoreCase(
  473. "xvmotionPlacement")) {
  474. List<ClusterAction> actions = clusterrecommend.getAction();
  475. for (ClusterAction action : actions) {
  476. if (action instanceof PlacementAction) {
  477. placementAction = (PlacementAction) action;
  478. break;
  479. }
  480. }
  481. if (placementAction != null) {
  482. if (placementAction.getVm() == null
  483. || placementAction.getTargetHost() == null) {
  484. System.out
  485. .println("Placement Action doesnot have a vm and targethost set");
  486. } else {
  487. if (placementAction.getRelocateSpec() != null) {
  488. actionOk = checkRelocateSpec(placementAction
  489. .getRelocateSpec());
  490. if (actionOk)
  491. break;
  492. else
  493. placementAction = null;
  494. }
  495. }
  496. } else {
  497. System.out
  498. .println("Recommendation doesnot have a placement action");
  499. }
  500. }
  501. }
  502. } else {
  503. System.out.println("No recommendations by DRS");
  504. }
  505. return placementAction;
  506. }
  507.  
  508. /**
  509. * This method is to validate the RelocateSpec.
  510. *
  511. * @param relocateSpec
  512. * @return boolean
  513. * @throws RuntimeFaultFaultMsg
  514. * @throws InvalidPropertyFaultMsg
  515. */
  516. boolean checkRelocateSpec(VirtualMachineRelocateSpec relocateSpec)
  517. throws RuntimeFaultFaultMsg, InvalidPropertyFaultMsg {
  518. boolean check = false;
  519. if (relocateSpec.getHost() != null) {
  520. if (relocateSpec.getPool() != null) {
  521. if (relocateSpec.getDatastore() != null) {
  522. check = true;
  523. } else {
  524. System.out.println("RelocateSpec doesnot have a datastore");
  525. }
  526. } else {
  527. System.out.println("RelocateSpec doesnot have a resource pool");
  528. }
  529. } else {
  530. System.out.println("RelocateSpec doesnot have a host");
  531. }
  532. return check;
  533. }
  534. }
  535.  
  536. /**
  537. * DestConnect Used to establish connection to the target Virtual Center.
  538. */
  539.  
  540. class DestConnect extends ConnectedVimServiceBase {
  541.  
  542. /**
  543. * This method is to establish the connection to the Target vCenter.
  544. *
  545. * @param url
  546. * , username, password
  547. * @return Connection
  548. */
  549. public GetMOREF destGetMOREFs;
  550.  
  551. Connection getDestConnection(String url, String username, String password) {
  552. Connection connection = new BasicConnection();
  553.  
  554. connection.setPassword(password);
  555. connection.setUrl(url);
  556. connection.setUsername(username);
  557. this.setHostConnection(true);
  558. this.setConnection(connection);
  559. Connection destConnection = this.connect();
  560. this.destGetMOREFs = getMOREFs;
  561. return destConnection;
  562. }
  563.  
  564. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement