locking: Change locking order, move NAL after instances
[ganeti-local] / test / ganeti.ovf_unittest.py
old mode 100644 (file)
new mode 100755 (executable)
index e30ac39..bc72496
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2011 Google Inc.
+# Copyright (C) 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -32,10 +32,16 @@ import sys
 import tempfile
 import unittest
 
+try:
+  import xml.etree.ElementTree as ET
+except ImportError:
+  import elementtree.ElementTree as ET
+
 from ganeti import constants
 from ganeti import errors
 from ganeti import ovf
 from ganeti import utils
+from ganeti import pathutils
 
 import testutils
 
@@ -53,6 +59,7 @@ GANETI_NETWORKS = {
   "nic0_ip": "none",
   "nic0_mac": "aa:00:00:d8:2c:1e",
   "nic0_link": "xen-br0",
+  "nic0_network": "auto",
 }
 GANETI_HYPERVISOR = {
   "hypervisor_name": "xen-pvm",
@@ -85,6 +92,7 @@ VIRTUALBOX_NETWORKS = {
   "nic0_ip": "none",
   "nic0_link": "auto",
   "nic0_mac": "auto",
+  "nic0_network": "auto",
 }
 VIRTUALBOX_HYPERVISOR = {"hypervisor_name": "auto"}
 VIRTUALBOX_OS = {"os_name": None}
@@ -124,6 +132,7 @@ CMDARGS_NETWORKS = {
   "nic0_ip": "none",
   "nic0_mac": "auto",
   "nic_count": "1",
+  "nic0_network": "auto",
 }
 CMDARGS_HYPERVISOR = {
   "hypervisor_name": "xen-pvm"
@@ -143,6 +152,10 @@ ARGS_EMPTY = {
   "nics": [],
   "disks": [],
   "name": "test-instance",
+  "ova_package": False,
+  "ext_usage": False,
+  "disk_format": "cow",
+  "compression": False,
 }
 ARGS_EXPORT_DIR = dict(ARGS_EMPTY, **{
   "output_dir": OUTPUT_DIR,
@@ -177,6 +190,142 @@ ARGS_BROKEN = dict(ARGS_EXPORT_DIR , **{
   "osparams": {},
 })
 
+EXP_ARGS_COMPRESSED = dict(ARGS_EXPORT_DIR, **{
+  "compression": True,
+})
+
+EXP_DISKS_LIST = [
+  {
+    "format": "vmdk",
+    "compression": "gzip",
+    "virt-size": 90000,
+    "real-size": 203,
+    "path": "new_disk.cow.gz",
+  },
+  {
+    "format": "cow",
+    "virt-size": 15,
+    "real-size": 15,
+    "path": "new_disk.cow",
+  },
+]
+EXP_NETWORKS_LIST = [
+  {"mac": "aa:00:00:d8:2c:1e", "ip":"None", "link":"br0",
+   "mode":"routed", "network": "test"},
+]
+EXP_PARTIAL_GANETI_DICT = {
+  "hypervisor": {"name": "xen-kvm"},
+  "os": {"name": "lenny-image"},
+  "auto_balance": "True",
+  "version": "0",
+}
+EXP_GANETI_DICT = {
+  "tags": None,
+  "auto_balance": "False",
+  "hypervisor": {
+     "root-path": "/dev/sda",
+     "name": "xen-pvm",
+     "kernel_args": "ro"
+   },
+  "version": "0",
+  "disk_template": None,
+  "os": {"name": "lenny-image"}
+}
+EXP_NAME ="xen-dev-i1"
+EXP_VCPUS = 1
+EXP_MEMORY = 512
+
+EXPORT_EMPTY = ("<Envelope xml:lang=\"en-US\" xmlns=\"http://schemas.dmtf.org/"
+                "ovf/envelope/1\" xmlns:gnt=\"http://ganeti\" xmlns:ovf=\""
+                "http://schemas.dmtf.org/ovf/envelope/1\" xmlns:rasd=\""
+                "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_Resource"
+                "AllocationSettingData\" xmlns:vssd=\"http://schemas.dmtf.org"
+                "/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData\""
+                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" />")
+EXPORT_DISKS_EMPTY = ("<References /><DiskSection><Info>Virtual disk"
+                      " information</Info></DiskSection>")
+EXPORT_DISKS = ("<References><File ovf:compression=\"gzip\" ovf:href=\"new_disk"
+                ".cow.gz\" ovf:id=\"file0\" ovf:size=\"203\" /><File ovf:href="
+                "\"new_disk.cow\" ovf:id=\"file1\" ovf:size=\"15\" />"
+                "</References><DiskSection><Info>Virtual disk information"
+                "</Info><Disk ovf:capacity=\"90000\" ovf:diskId=\"disk0\" ovf"
+                ":fileRef=\"file0\" ovf:format=\"http://www.vmware.com/"
+                "interfaces/specifications/vmdk.html#monolithicSparse\" /><Disk"
+                " ovf:capacity=\"15\" ovf:diskId=\"disk1\" ovf:fileRef"
+                "=\"file1\" ovf:format=\"http://www.gnome.org/~markmc/qcow"
+                "-image-format.html\" /></DiskSection>")
+EXPORT_NETWORKS_EMPTY = ("<NetworkSection><Info>List of logical networks</Info>"
+                         "</NetworkSection>")
+EXPORT_NETWORKS = ("<NetworkSection><Info>List of logical networks</Info>"
+                   "<Network ovf:name=\"routed0\" /></NetworkSection>")
+EXPORT_GANETI_INCOMPLETE = ("<gnt:GanetiSection><gnt:Version>0</gnt:Version>"
+                            "<gnt:AutoBalance>True</gnt:AutoBalance><gnt:"
+                            "OperatingSystem><gnt:Name>lenny-image</gnt:Name>"
+                            "<gnt:Parameters /></gnt:OperatingSystem><gnt:"
+                            "Hypervisor><gnt:Name>xen-kvm</gnt:Name><gnt:"
+                            "Parameters /></gnt:Hypervisor><gnt:Network><gnt:"
+                            "Nic ovf:name=\"routed0\"><gnt:Mode>routed</gnt:"
+                            "Mode><gnt:MACAddress>aa:00:00:d8:2c:1e</gnt:"
+                            "MACAddress><gnt:IPAddress>None</gnt:IPAddress>"
+                            "<gnt:Link>br0</gnt:Link><gnt:Net>test</gnt:Net>"
+                            "</gnt:Nic></gnt:Network></gnt:GanetiSection>")
+EXPORT_GANETI = ("<gnt:GanetiSection><gnt:Version>0</gnt:Version><gnt:"
+                 "AutoBalance>False</gnt:AutoBalance><gnt:OperatingSystem>"
+                 "<gnt:Name>lenny-image</gnt:Name><gnt:Parameters /></gnt:"
+                 "OperatingSystem><gnt:Hypervisor><gnt:Name>xen-pvm</gnt:Name>"
+                 "<gnt:Parameters><gnt:root-path>/dev/sda</gnt:root-path><gnt:"
+                 "kernel_args>ro</gnt:kernel_args></gnt:Parameters></gnt:"
+                 "Hypervisor><gnt:Network><gnt:Nic ovf:name=\"routed0\"><gnt:"
+                 "Mode>routed</gnt:Mode><gnt:MACAddress>aa:00:00:d8:2c:1e</gnt:"
+                 "MACAddress><gnt:IPAddress>None</gnt:IPAddress><gnt:Link>br0"
+                 "</gnt:Link><gnt:Net>test</gnt:Net></gnt:Nic></gnt:Network>"
+                 "</gnt:GanetiSection>")
+EXPORT_SYSTEM = ("<References><File ovf:compression=\"gzip\" ovf:href=\"new_"
+                 "disk.cow.gz\" ovf:id=\"file0\" ovf:size=\"203\" /><File ovf:"
+                 "href=\"new_disk.cow\" ovf:id=\"file1\" ovf:size=\"15\" />"
+                 "</References><DiskSection><Info>Virtual disk information"
+                 "</Info><Disk ovf:capacity=\"90000\" ovf:diskId=\"disk0\""
+                 " ovf:fileRef=\"file0\" ovf:format=\"http://www.vmware.com"
+                 "/interfaces/specifications/vmdk.html#monolithicSparse\" />"
+                 "<Disk ovf:capacity=\"15\" ovf:diskId=\"disk1\" ovf:fileRef"
+                 "=\"file1\" ovf:format=\"http://www.gnome.org/~markmc/qcow"
+                 "-image-format.html\" /></DiskSection><NetworkSection><Info>"
+                 "List of logical networks</Info><Network ovf:name=\"routed0\""
+                 " /></NetworkSection><VirtualSystem ovf:id=\"xen-dev-i1\">"
+                 "<Info>A virtual machine</Info><Name>xen-dev-i1</Name>"
+                 "<OperatingSystemSection ovf:id=\"0\"><Info>Installed guest"
+                 " operating system</Info></OperatingSystemSection><Virtual"
+                 "HardwareSection><Info>Virtual hardware requirements</Info>"
+                 "<System><vssd:ElementName>Virtual Hardware Family"
+                 "</vssd:ElementName><vssd:InstanceID>0</vssd:InstanceID><vssd:"
+                 "VirtualSystemIdentifier>xen-dev-i1</vssd:VirtualSystem"
+                 "Identifier><vssd:VirtualSystemType>ganeti-ovf</vssd:Virtual"
+                 "SystemType></System><Item><rasd:ElementName>1 virtual CPU(s)"
+                 "</rasd:ElementName><rasd:InstanceID>1</rasd:InstanceID><rasd:"
+                 "ResourceType>3</rasd:ResourceType><rasd:VirtualQuantity>1"
+                 "</rasd:VirtualQuantity></Item><Item><rasd:AllocationUnits>"
+                 "byte * 2^20</rasd:AllocationUnits><rasd:ElementName>512MB of"
+                 " memory</rasd:ElementName><rasd:InstanceID>2</rasd:"
+                 "InstanceID><rasd:ResourceType>4</rasd:ResourceType><rasd:"
+                 "VirtualQuantity>512</rasd:VirtualQuantity></Item><Item>"
+                 "<rasd:Address>0</rasd:Address><rasd:ElementName>scsi"
+                 "_controller0</rasd:ElementName><rasd:InstanceID>3"
+                 "</rasd:InstanceID><rasd:ResourceSubType>lsilogic</rasd"
+                 ":ResourceSubType><rasd:ResourceType>6</rasd:ResourceType>"
+                 "</Item><Item><rasd:ElementName>disk0</rasd:ElementName><rasd"
+                 ":HostResource>ovf:/disk/disk0</rasd:HostResource><rasd"
+                 ":InstanceID>4</rasd:InstanceID><rasd:Parent>3</rasd:Parent>"
+                 "<rasd:ResourceType>17</rasd:ResourceType></Item><Item><rasd:"
+                 "ElementName>disk1</rasd:ElementName><rasd:HostResource>ovf:/"
+                 "disk/disk1</rasd:HostResource><rasd:InstanceID>5</rasd"
+                 ":InstanceID><rasd:Parent>3</rasd:Parent><rasd:ResourceType>17"
+                 "</rasd:ResourceType></Item><Item><rasd:Address>aa:00"
+                 ":00:d8:2c:1e</rasd:Address><rasd:Connection>routed0</rasd"
+                 ":Connection><rasd:ElementName>routed0</rasd:ElementName><rasd"
+                 ":InstanceID>6</rasd:InstanceID><rasd:ResourceType>10</rasd"
+                 ":ResourceType></Item></VirtualHardwareSection>"
+                 "</VirtualSystem>")
+
 
 def _GetArgs(args, with_name=False):
   options = optparse.Values()
@@ -190,6 +339,10 @@ def _GetArgs(args, with_name=False):
 OPTS_EMPTY = _GetArgs(ARGS_EMPTY)
 OPTS_EXPORT_NO_NAME = _GetArgs(ARGS_EXPORT_DIR)
 OPTS_EXPORT = _GetArgs(ARGS_EXPORT_DIR, with_name=True)
+
+EXP_OPTS = OPTS_EXPORT_NO_NAME
+EXP_OPTS_COMPRESSED = _GetArgs(EXP_ARGS_COMPRESSED)
+
 OPTS_VBOX = _GetArgs(ARGS_VBOX)
 OPTS_COMPLETE = _GetArgs(ARGS_COMPLETE)
 OPTS_NONIC_NODISK = _GetArgs(ARGS_BROKEN)
@@ -247,14 +400,14 @@ class TestOVFImporter(BetterUnitTest):
   def testOVAUnpackingDirectories(self):
     self.importer = ovf.OVFImporter(self.ova_package, OPTS_EMPTY)
     self.assertTrue(self.importer.input_dir != None)
-    self.assertEquals(self.importer.output_dir , constants.EXPORT_DIR)
+    self.assertEquals(self.importer.output_dir , pathutils.EXPORT_DIR)
     self.assertTrue(self.importer.temp_dir != None)
 
   def testOVFUnpackingDirectories(self):
     self.importer = ovf.OVFImporter(self.virtualbox_ovf,
       OPTS_EMPTY)
     self.assertEquals(self.importer.input_dir , _GetFullFilename(""))
-    self.assertEquals(self.importer.output_dir , constants.EXPORT_DIR)
+    self.assertEquals(self.importer.output_dir , pathutils.EXPORT_DIR)
     self.assertEquals(self.importer.temp_dir , None)
 
   def testOVFSetOutputDirDirectories(self):
@@ -386,6 +539,92 @@ class TestOVFImporter(BetterUnitTest):
       " description", self.importer.Parse)
 
 
+class TestOVFExporter(BetterUnitTest):
+  def setUp(self):
+    self.exporter = None
+    self.wrong_config_file = _GetFullFilename("wrong_config.ini")
+    self.unsafe_path_to_disk = _GetFullFilename("unsafe_path.ini")
+    self.disk_image_not_exist = _GetFullFilename("no_disk.ini")
+    self.empty_config = _GetFullFilename("empty.ini")
+    self.standard_export = _GetFullFilename("config.ini")
+    self.wrong_network_mode = self.disk_image_not_exist
+    self.no_memory = self.disk_image_not_exist
+    self.no_vcpus = self.disk_image_not_exist
+    self.no_os = _GetFullFilename("no_os.ini")
+    self.no_hypervisor = self.disk_image_not_exist
+
+  def tearDown(self):
+    if self.exporter:
+      self.exporter.Cleanup()
+    del_dir = os.path.abspath(OUTPUT_DIR)
+    try:
+      shutil.rmtree(del_dir)
+    except OSError:
+      pass
+
+  def testErrorWrongConfigFile(self):
+    self.assertRaisesRegexp(errors.OpPrereqError,
+      "Error when trying to read", ovf.OVFExporter,
+      self.wrong_config_file, EXP_OPTS)
+
+  def testErrorPathToTheDiskIncorrect(self):
+    self.exporter = ovf.OVFExporter(self.unsafe_path_to_disk, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError, "contains a directory name",
+      self.exporter._ParseDisks)
+
+  def testErrorDiskImageNotExist(self):
+    self.exporter = ovf.OVFExporter(self.disk_image_not_exist, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError, "Disk image does not exist",
+      self.exporter._ParseDisks)
+
+  def testParseNetworks(self):
+    self.exporter = ovf.OVFExporter(self.standard_export, EXP_OPTS)
+    results = self.exporter._ParseNetworks()
+    self.assertEqual(results, EXP_NETWORKS_LIST)
+
+  def testErrorWrongNetworkMode(self):
+    self.exporter = ovf.OVFExporter(self.wrong_network_mode, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError,
+      "Network mode nic not recognized", self.exporter._ParseNetworks)
+
+  def testParseVCPusMem(self):
+    self.exporter = ovf.OVFExporter(self.standard_export, EXP_OPTS)
+    vcpus = self.exporter._ParseVCPUs()
+    memory = self.exporter._ParseMemory()
+    self.assertEqual(vcpus, EXP_VCPUS)
+    self.assertEqual(memory, EXP_MEMORY)
+
+  def testErrorNoVCPUs(self):
+    self.exporter = ovf.OVFExporter(self.no_vcpus, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError, "No CPU information found",
+      self.exporter._ParseVCPUs)
+
+  def testErrorNoMemory(self):
+    self.exporter = ovf.OVFExporter(self.no_memory, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError, "No memory information found",
+      self.exporter._ParseMemory)
+
+  def testParseGaneti(self):
+    self.exporter = ovf.OVFExporter(self.standard_export, EXP_OPTS)
+    results = self.exporter._ParseGaneti()
+    self.assertEqual(results, EXP_GANETI_DICT)
+
+  def testErrorNoHypervisor(self):
+    self.exporter = ovf.OVFExporter(self.no_hypervisor, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError,
+      "No hypervisor information found", self.exporter._ParseGaneti)
+
+  def testErrorNoOS(self):
+    self.exporter = ovf.OVFExporter(self.no_os, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError,
+      "No operating system information found", self.exporter._ParseGaneti)
+
+  def testErrorParseNoInstanceName(self):
+    self.exporter = ovf.OVFExporter(self.empty_config, EXP_OPTS)
+    self.assertRaisesRegexp(errors.OpPrereqError, "No instance name found",
+      self.exporter.Parse)
+
+
 class TestOVFReader(BetterUnitTest):
   def setUp(self):
     self.wrong_xml_file = _GetFullFilename("wrong_xml.ovf")
@@ -532,5 +771,54 @@ class TestOVFReader(BetterUnitTest):
     self.assertEqual(version, EMPTY_VERSION)
 
 
+class TestOVFWriter(BetterUnitTest):
+  def setUp(self):
+    self.writer = ovf.OVFWriter(True)
+
+  def tearDown(self):
+    pass
+
+  def testOVFWriterInit(self):
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_EMPTY in result)
+
+  def testSaveDisksDataEmpty(self):
+    self.writer.SaveDisksData([])
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_DISKS_EMPTY in result)
+
+  def testSaveDisksData(self):
+    self.writer.SaveDisksData(EXP_DISKS_LIST)
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_DISKS in result)
+
+  def testSaveNetworkDataEmpty(self):
+    self.writer.SaveNetworksData([])
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_NETWORKS_EMPTY in result)
+
+  def testSaveNetworksData(self):
+    self.writer.SaveNetworksData(EXP_NETWORKS_LIST)
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_NETWORKS in result)
+
+  def testSaveGanetiDataIncomplete(self):
+    self.writer.SaveGanetiData(EXP_PARTIAL_GANETI_DICT, EXP_NETWORKS_LIST)
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_GANETI_INCOMPLETE in result)
+
+  def testSaveGanetiDataComplete(self):
+    self.writer.SaveGanetiData(EXP_GANETI_DICT, EXP_NETWORKS_LIST)
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_GANETI in result)
+
+  def testSaveVirtualSystem(self):
+    self.writer.SaveDisksData(EXP_DISKS_LIST)
+    self.writer.SaveNetworksData(EXP_NETWORKS_LIST)
+    self.writer.SaveVirtualSystemData(EXP_NAME, EXP_VCPUS, EXP_MEMORY)
+    result = ET.tostring(self.writer.tree)
+    self.assertTrue(EXPORT_SYSTEM in result)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()