|
17 | 17 | # under the License. |
18 | 18 | """ BVT tests for Network Life Cycle |
19 | 19 | """ |
| 20 | +import json |
| 21 | + |
20 | 22 | # Import Local Modules |
21 | 23 | from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE, |
22 | 24 | NAT_RULE, PASS) |
23 | 25 | from marvin.cloudstackTestCase import cloudstackTestCase |
24 | 26 | from marvin.cloudstackException import CloudstackAPIException |
25 | 27 | from marvin.cloudstackAPI import rebootRouter |
26 | 28 | from marvin.sshClient import SshClient |
27 | | -from marvin.lib.utils import cleanup_resources, get_process_status, get_host_credentials |
| 29 | +from marvin.lib.utils import cleanup_resources, get_process_status, get_host_credentials, random_gen |
28 | 30 | from marvin.lib.base import (Account, |
29 | 31 | VirtualMachine, |
30 | 32 | ServiceOffering, |
|
37 | 39 | LoadBalancerRule, |
38 | 40 | Router, |
39 | 41 | NIC, |
40 | | - Cluster) |
| 42 | + Template, |
| 43 | + Cluster, |
| 44 | + SSHKeyPair) |
41 | 45 | from marvin.lib.common import (get_domain, |
42 | 46 | get_free_vlan, |
43 | 47 | get_zone, |
|
58 | 62 | from ddt import ddt, data |
59 | 63 | import unittest |
60 | 64 | # Import System modules |
| 65 | +import os |
61 | 66 | import time |
62 | 67 | import logging |
63 | 68 | import random |
| 69 | +import tempfile |
64 | 70 |
|
65 | 71 | _multiprocess_shared_ = True |
66 | 72 |
|
@@ -2113,3 +2119,313 @@ def test_03_destroySharedNetwork(self): |
2113 | 2119 | 0, |
2114 | 2120 | "Failed to find the placeholder IP" |
2115 | 2121 | ) |
| 2122 | + |
| 2123 | + |
| 2124 | +class TestSharedNetworkWithConfigDrive(cloudstackTestCase): |
| 2125 | + |
| 2126 | + @classmethod |
| 2127 | + def setUpClass(cls): |
| 2128 | + cls.testClient = super(TestSharedNetworkWithConfigDrive, cls).getClsTestClient() |
| 2129 | + cls.apiclient = cls.testClient.getApiClient() |
| 2130 | + |
| 2131 | + cls.services = cls.testClient.getParsedTestDataConfig() |
| 2132 | + # Get Zone, Domain and templates |
| 2133 | + cls.domain = get_domain(cls.apiclient) |
| 2134 | + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) |
| 2135 | + cls.hv = cls.testClient.getHypervisorInfo() |
| 2136 | + |
| 2137 | + if cls.hv.lower() == 'simulator': |
| 2138 | + cls.skip = True |
| 2139 | + return |
| 2140 | + else: |
| 2141 | + cls.skip = False |
| 2142 | + |
| 2143 | + cls._cleanup = [] |
| 2144 | + |
| 2145 | + template = Template.register( |
| 2146 | + cls.apiclient, |
| 2147 | + cls.services["test_templates_cloud_init"][cls.hv], |
| 2148 | + zoneid=cls.zone.id, |
| 2149 | + hypervisor=cls.hv, |
| 2150 | + ) |
| 2151 | + template.download(cls.apiclient) |
| 2152 | + cls._cleanup.append(template) |
| 2153 | + |
| 2154 | + cls.services["virtual_machine"]["zoneid"] = cls.zone.id |
| 2155 | + cls.services["virtual_machine"]["template"] = template.id |
| 2156 | + cls.services["virtual_machine"]["username"] = "ubuntu" |
| 2157 | + # Create Network Offering |
| 2158 | + cls.services["shared_network_offering_configdrive"]["specifyVlan"] = "True" |
| 2159 | + cls.services["shared_network_offering_configdrive"]["specifyIpRanges"] = "True" |
| 2160 | + cls.shared_network_offering = NetworkOffering.create(cls.apiclient, |
| 2161 | + cls.services["shared_network_offering_configdrive"], |
| 2162 | + conservemode=True) |
| 2163 | + |
| 2164 | + cls.isolated_network_offering = NetworkOffering.create( |
| 2165 | + cls.apiclient, |
| 2166 | + cls.services["isolated_network_offering"], |
| 2167 | + conservemode=True |
| 2168 | + ) |
| 2169 | + |
| 2170 | + # Update network offering state from disabled to enabled. |
| 2171 | + NetworkOffering.update( |
| 2172 | + cls.isolated_network_offering, |
| 2173 | + cls.apiclient, |
| 2174 | + id=cls.isolated_network_offering.id, |
| 2175 | + state="enabled" |
| 2176 | + ) |
| 2177 | + |
| 2178 | + # Update network offering state from disabled to enabled. |
| 2179 | + NetworkOffering.update(cls.shared_network_offering, cls.apiclient, state="enabled") |
| 2180 | + |
| 2181 | + cls.service_offering = ServiceOffering.create(cls.apiclient, cls.services["service_offering"]) |
| 2182 | + physical_network, vlan = get_free_vlan(cls.apiclient, cls.zone.id) |
| 2183 | + # create network using the shared network offering created |
| 2184 | + |
| 2185 | + cls.services["shared_network"]["acltype"] = "domain" |
| 2186 | + cls.services["shared_network"]["vlan"] = vlan |
| 2187 | + cls.services["shared_network"]["networkofferingid"] = cls.shared_network_offering.id |
| 2188 | + cls.services["shared_network"]["physicalnetworkid"] = physical_network.id |
| 2189 | + |
| 2190 | + cls.setSharedNetworkParams("shared_network") |
| 2191 | + cls.shared_network = Network.create(cls.apiclient, |
| 2192 | + cls.services["shared_network"], |
| 2193 | + networkofferingid=cls.shared_network_offering.id, |
| 2194 | + zoneid=cls.zone.id) |
| 2195 | + |
| 2196 | + cls.isolated_network = Network.create( |
| 2197 | + cls.apiclient, |
| 2198 | + cls.services["isolated_network"], |
| 2199 | + networkofferingid=cls.isolated_network_offering.id, |
| 2200 | + zoneid=cls.zone.id |
| 2201 | + ) |
| 2202 | + |
| 2203 | + cls._cleanup.extend([ |
| 2204 | + cls.shared_network, |
| 2205 | + cls.service_offering, |
| 2206 | + cls.shared_network, |
| 2207 | + cls.shared_network_offering, |
| 2208 | + cls.isolated_network |
| 2209 | + ]) |
| 2210 | + cls.tmp_files = [] |
| 2211 | + cls.keypair = cls.generate_ssh_keys() |
| 2212 | + return |
| 2213 | + |
| 2214 | + @classmethod |
| 2215 | + def generate_ssh_keys(cls): |
| 2216 | + """Generates ssh key pair |
| 2217 | +
|
| 2218 | + Writes the private key into a temp file and returns the file name |
| 2219 | +
|
| 2220 | + :returns: generated keypair |
| 2221 | + :rtype: MySSHKeyPair |
| 2222 | + """ |
| 2223 | + cls.keypair = SSHKeyPair.create( |
| 2224 | + cls.apiclient, |
| 2225 | + name=random_gen() + ".pem") |
| 2226 | + |
| 2227 | + cls._cleanup.append(SSHKeyPair(cls.keypair.__dict__, None)) |
| 2228 | + cls.debug("Created keypair with name: %s" % cls.keypair.name) |
| 2229 | + cls.debug("Writing the private key to local file") |
| 2230 | + pkfile = tempfile.gettempdir() + os.sep + cls.keypair.name |
| 2231 | + cls.keypair.private_key_file = pkfile |
| 2232 | + cls.tmp_files.append(pkfile) |
| 2233 | + cls.debug("File path: %s" % pkfile) |
| 2234 | + with open(pkfile, "w+") as f: |
| 2235 | + f.write(cls.keypair.privatekey) |
| 2236 | + os.chmod(pkfile, 0o400) |
| 2237 | + |
| 2238 | + return cls.keypair |
| 2239 | + |
| 2240 | + def setUp(self): |
| 2241 | + self.apiclient = self.testClient.getApiClient() |
| 2242 | + self.dbclient = self.testClient.getDbConnection() |
| 2243 | + if self.skip: |
| 2244 | + self.skipTest("Hypervisor is simulator - skipping Test..") |
| 2245 | + self.cleanup = [] |
| 2246 | + |
| 2247 | + @classmethod |
| 2248 | + def tearDownClass(cls): |
| 2249 | + try: |
| 2250 | + # Cleanup resources used |
| 2251 | + cleanup_resources(cls.apiclient, cls._cleanup) |
| 2252 | + for tmp_file in cls.tmp_files: |
| 2253 | + os.remove(tmp_file) |
| 2254 | + except Exception as e: |
| 2255 | + raise Exception("Warning: Exception during cleanup : %s" % e) |
| 2256 | + return |
| 2257 | + |
| 2258 | + def tearDown(self): |
| 2259 | + cleanup_resources(self.apiclient, self.cleanup) |
| 2260 | + return |
| 2261 | + |
| 2262 | + @classmethod |
| 2263 | + def setSharedNetworkParams(cls, network, range=20): |
| 2264 | + |
| 2265 | + # @range: range decides the endip. Pass the range as "x" if you want the difference between the startip |
| 2266 | + # and endip as "x" |
| 2267 | + # Set the subnet number of shared networks randomly prior to execution |
| 2268 | + # of each test case to avoid overlapping of ip addresses |
| 2269 | + shared_network_subnet_number = random.randrange(1, 254) |
| 2270 | + cls.services[network]["gateway"] = "172.16." + str(shared_network_subnet_number) + ".1" |
| 2271 | + cls.services[network]["startip"] = "172.16." + str(shared_network_subnet_number) + ".2" |
| 2272 | + cls.services[network]["endip"] = "172.16." + str(shared_network_subnet_number) + "." + str(range + 1) |
| 2273 | + cls.services[network]["netmask"] = "255.255.255.0" |
| 2274 | + logger.debug("Executing command '%s'" % cls.services[network]) |
| 2275 | + |
| 2276 | + def _mount_config_drive(self, ssh): |
| 2277 | + """ |
| 2278 | + This method is to verify whether configdrive iso |
| 2279 | + is attached to vm or not |
| 2280 | + Returns mount path if config drive is attached else None |
| 2281 | + """ |
| 2282 | + mountdir = "/root/iso" |
| 2283 | + cmd = "sudo blkid -t LABEL='config-2' " \ |
| 2284 | + "/dev/sr? /dev/hd? /dev/sd? /dev/xvd? -o device" |
| 2285 | + tmp_cmd = [ |
| 2286 | + 'sudo bash -c "if [ ! -d {0} ]; then mkdir {0}; fi"'.format(mountdir), |
| 2287 | + "sudo umount %s" % mountdir] |
| 2288 | + self.debug("Unmounting drive from %s" % mountdir) |
| 2289 | + for tcmd in tmp_cmd: |
| 2290 | + ssh.execute(tcmd) |
| 2291 | + |
| 2292 | + self.debug("Trying to find ConfigDrive device") |
| 2293 | + configDrive = ssh.execute(cmd) |
| 2294 | + if not configDrive: |
| 2295 | + self.warn("ConfigDrive is not attached") |
| 2296 | + return None |
| 2297 | + |
| 2298 | + res = ssh.execute("sudo mount {} {}".format(str(configDrive[0]), mountdir)) |
| 2299 | + if str(res).lower().find("read-only") > -1: |
| 2300 | + self.debug("ConfigDrive iso is mounted at location %s" % mountdir) |
| 2301 | + return mountdir |
| 2302 | + else: |
| 2303 | + return None |
| 2304 | + |
| 2305 | + def _umount_config_drive(self, ssh, mount_path): |
| 2306 | + """unmount config drive inside guest vm |
| 2307 | +
|
| 2308 | + :param ssh: SSH connection to the VM |
| 2309 | + :type ssh: marvin.sshClient.SshClient |
| 2310 | + :type mount_path: str |
| 2311 | + """ |
| 2312 | + ssh.execute("sudo umount -d %s" % mount_path) |
| 2313 | + # Give the VM time to unlock the iso device |
| 2314 | + time.sleep(0.5) |
| 2315 | + # Verify umount |
| 2316 | + result = ssh.execute("sudo ls %s" % mount_path) |
| 2317 | + self.assertTrue(len(result) == 0, |
| 2318 | + "After umount directory should be empty " |
| 2319 | + "but contains: %s" % result) |
| 2320 | + |
| 2321 | + def _get_config_drive_data(self, ssh, file, name, fail_on_missing=True): |
| 2322 | + """Fetches the content of a file file on the config drive |
| 2323 | +
|
| 2324 | + :param ssh: SSH connection to the VM |
| 2325 | + :param file: path to the file to fetch |
| 2326 | + :param name: description of the file |
| 2327 | + :param fail_on_missing: |
| 2328 | + whether the test should fail if the file is missing |
| 2329 | + :type ssh: marvin.sshClient.SshClient |
| 2330 | + :type file: str |
| 2331 | + :type name: str |
| 2332 | + :type fail_on_missing: bool |
| 2333 | + :returns: the content of the file |
| 2334 | + :rtype: str |
| 2335 | + """ |
| 2336 | + cmd = "sudo cat %s" % file |
| 2337 | + res = ssh.execute(cmd) |
| 2338 | + content = '\n'.join(res) |
| 2339 | + |
| 2340 | + if fail_on_missing and "No such file or directory" in content: |
| 2341 | + self.debug("{} is not found".format(name)) |
| 2342 | + self.fail("{} is not found".format(name)) |
| 2343 | + |
| 2344 | + return content |
| 2345 | + |
| 2346 | + def _get_ip_address_output(self, ssh): |
| 2347 | + cmd = "ip address" |
| 2348 | + res = ssh.execute(cmd) |
| 2349 | + return '\n'.join(res) |
| 2350 | + |
| 2351 | + @attr(tags=["advanced", "shared"], required_hardware="true") |
| 2352 | + def test_01_deployVMInSharedNetwork(self): |
| 2353 | + try: |
| 2354 | + self.virtual_machine = VirtualMachine.create(self.apiclient, self.services["virtual_machine"], |
| 2355 | + networkids=[self.shared_network.id, self.isolated_network.id], |
| 2356 | + serviceofferingid=self.service_offering.id, |
| 2357 | + keypair=self.keypair.name |
| 2358 | + ) |
| 2359 | + self.cleanup.append(self.virtual_machine) |
| 2360 | + except Exception as e: |
| 2361 | + self.fail("Exception while deploying virtual machine: %s" % e) |
| 2362 | + |
| 2363 | + public_ips = list_publicIP( |
| 2364 | + self.apiclient, |
| 2365 | + associatednetworkid=self.isolated_network.id |
| 2366 | + ) |
| 2367 | + public_ip = public_ips[0] |
| 2368 | + FireWallRule.create( |
| 2369 | + self.apiclient, |
| 2370 | + ipaddressid=public_ip.id, |
| 2371 | + protocol=self.services["natrule"]["protocol"], |
| 2372 | + cidrlist=['0.0.0.0/0'], |
| 2373 | + startport=self.services["natrule"]["publicport"], |
| 2374 | + endport=self.services["natrule"]["publicport"] |
| 2375 | + ) |
| 2376 | + |
| 2377 | + nat_rule = NATRule.create( |
| 2378 | + self.apiclient, |
| 2379 | + self.virtual_machine, |
| 2380 | + self.services["natrule"], |
| 2381 | + public_ip.id |
| 2382 | + ) |
| 2383 | + |
| 2384 | + private_key_file_location = self.keypair.private_key_file if self.keypair else None |
| 2385 | + ssh = self.virtual_machine.get_ssh_client(ipaddress=nat_rule.ipaddress, |
| 2386 | + keyPairFileLocation=private_key_file_location, retries=5) |
| 2387 | + |
| 2388 | + mount_path = self._mount_config_drive(ssh) |
| 2389 | + |
| 2390 | + network_data_content = self._get_config_drive_data(ssh, mount_path + "/openstack/latest/network_data.json", |
| 2391 | + "network_data") |
| 2392 | + |
| 2393 | + network_data = json.loads(network_data_content) |
| 2394 | + |
| 2395 | + self._umount_config_drive(ssh, mount_path) |
| 2396 | + |
| 2397 | + ip_address_output = self._get_ip_address_output(ssh) |
| 2398 | + |
| 2399 | + self.assertTrue('links' in network_data, "network_data.json doesn't contain links") |
| 2400 | + self.assertTrue('networks' in network_data, "network_data.json doesn't contain networks") |
| 2401 | + self.assertTrue('services' in network_data, "network_data.json doesn't contain services") |
| 2402 | + |
| 2403 | + for x in ['links', 'networks', 'services']: |
| 2404 | + self.assertTrue(x in network_data, "network_data.json doesn't contain " + x) |
| 2405 | + self.assertEqual(len(network_data[x]), 2, "network_data.json doesn't contain 2 " + x) |
| 2406 | + |
| 2407 | + self.assertIn(network_data['links'][0]['ethernet_mac_address'], |
| 2408 | + [self.virtual_machine.nic[0].macaddress, self.virtual_machine.nic[1].macaddress], |
| 2409 | + "macaddress doesn't match") |
| 2410 | + self.assertIn(network_data['links'][1]['ethernet_mac_address'], |
| 2411 | + [self.virtual_machine.nic[0].macaddress, self.virtual_machine.nic[1].macaddress], |
| 2412 | + "macaddress doesn't match") |
| 2413 | + |
| 2414 | + self.assertIn(network_data['networks'][0]['ip_address'], |
| 2415 | + [self.virtual_machine.nic[0].ipaddress, self.virtual_machine.nic[1].ipaddress], |
| 2416 | + "ip address doesn't match") |
| 2417 | + self.assertIn(network_data['networks'][1]['ip_address'], |
| 2418 | + [self.virtual_machine.nic[0].ipaddress, self.virtual_machine.nic[1].ipaddress], |
| 2419 | + "ip address doesn't match") |
| 2420 | + self.assertIn(network_data['networks'][0]['netmask'], |
| 2421 | + [self.virtual_machine.nic[0].netmask, self.virtual_machine.nic[1].netmask], |
| 2422 | + "netmask doesn't match") |
| 2423 | + self.assertIn(network_data['networks'][1]['netmask'], |
| 2424 | + [self.virtual_machine.nic[0].netmask, self.virtual_machine.nic[1].netmask], |
| 2425 | + "netmask doesn't match") |
| 2426 | + |
| 2427 | + self.assertEqual(network_data['services'][0]['type'], 'dns', "network_data.json doesn't contain dns service") |
| 2428 | + self.assertEqual(network_data['services'][1]['type'], 'dns', "network_data.json doesn't contain dns service") |
| 2429 | + |
| 2430 | + self.assertTrue(self.virtual_machine.nic[0].ipaddress in ip_address_output, "ip address doesn't match") |
| 2431 | + self.assertTrue(self.virtual_machine.nic[1].ipaddress in ip_address_output, "ip address doesn't match") |
0 commit comments