From 394d549c07d71eaa2f77cfdd43d7fa8890f852a8 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Thu, 12 Mar 2020 15:32:47 -0400 Subject: [PATCH] Support image uploads in 'info' CLI command Change the 'info' command output to include image upload data. For each image, we'll now output each build and the uploads for the build. Change-Id: Ib25ce30d30ed718b2b6083c2127fdb214c3691f4 --- nodepool/cmd/nodepoolcmd.py | 13 ++++++----- nodepool/tests/unit/test_commands.py | 33 ++++++++++++++++++++-------- nodepool/tests/unit/test_zk.py | 19 ++++++++++++++++ nodepool/zk.py | 29 ++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/nodepool/cmd/nodepoolcmd.py b/nodepool/cmd/nodepoolcmd.py index 1bba30f82..987b7e3df 100644 --- a/nodepool/cmd/nodepoolcmd.py +++ b/nodepool/cmd/nodepoolcmd.py @@ -316,16 +316,19 @@ class NodePoolCmd(NodepoolApp): def info(self): provider_name = self.args.provider - provider_builds = self.zk.getProviderBuilds(provider_name) + provider_uploads = self.zk.getProviderUploads(provider_name) provider_nodes = self.zk.getProviderNodes(provider_name) print("ZooKeeper data for provider %s\n" % provider_name) - print("Image builds:") - t = PrettyTable(['Image Name', 'Build IDs']) + print("Image data:") + t = PrettyTable(['Image Name', 'Build ID', 'Upload IDs']) t.align = 'l' - for image, builds in provider_builds.items(): - t.add_row([image, ','.join(builds)]) + for image in sorted(provider_uploads): + for build in sorted(provider_uploads[image]): + uploads = provider_uploads[image][build] + upload_ids = sorted([u.id for u in uploads]) + t.add_row([image, build, ','.join(upload_ids)]) print(t) print("\nNodes:") diff --git a/nodepool/tests/unit/test_commands.py b/nodepool/tests/unit/test_commands.py index 373b52a53..ec195f336 100644 --- a/nodepool/tests/unit/test_commands.py +++ b/nodepool/tests/unit/test_commands.py @@ -51,7 +51,7 @@ class TestNodepoolCMD(tests.DBTestCase): if col_count: self.assertEquals(len(row), col_count) log.debug(row) - if row[col] == val: + if col < len(row) and row[col] == val: rows_with_val += 1 self.assertEquals(rows_with_val, count) break @@ -320,6 +320,7 @@ class TestNodepoolCMD(tests.DBTestCase): self.useBuilder(configfile) pool.start() p1_image = self.waitForImage('fake-provider', 'fake-image') + p2_image = self.waitForImage('fake-provider2', 'fake-image') p1_nodes = self.waitForNodes('fake-label') p2_nodes = self.waitForNodes('fake-label2') @@ -328,33 +329,47 @@ class TestNodepoolCMD(tests.DBTestCase): # recreate the data. self.replace_config(configfile, 'info_cmd_two_provider_remove.yaml') + IMAGE_NAME_COL = 0 + BUILD_ID_COL = 1 + UPLOAD_ID_COL = 2 + NODE_ID_COL = 0 + # Verify that the second provider image is listed self.assert_listed( configfile, ['info', 'fake-provider2'], - 0, 'fake-image', 1) - - # Verify that the second provider node is listed. + IMAGE_NAME_COL, 'fake-image', 1) self.assert_listed( configfile, ['info', 'fake-provider2'], - 0, p2_nodes[0].id, 1) + BUILD_ID_COL, p2_image.build_id, 1) + self.assert_listed( + configfile, + ['info', 'fake-provider2'], + UPLOAD_ID_COL, p2_image.id, 1) + + # Verify that the second provider node is listed in the second table. + self.assert_listed( + configfile, + ['info', 'fake-provider2'], + NODE_ID_COL, p2_nodes[0].id, 1) # Erase the data for the second provider self.patch_argv( "-c", configfile, 'erase', 'fake-provider2', '--force') nodepoolcmd.main() - # Verify that no build or node for the second provider is listed - # after the previous erase + # Verify that no image or node for the second provider is listed + # after the previous erase. With no build data, the image name should + # not even show up. self.assert_listed( configfile, ['info', 'fake-provider2'], - 0, 'fake-image', 0) + IMAGE_NAME_COL, 'fake-image', 0) self.assert_listed( configfile, ['info', 'fake-provider2'], - 0, p2_nodes[0].id, 0) + NODE_ID_COL, p2_nodes[0].id, 0) # Verify that we did not affect the first provider image = self.waitForImage('fake-provider', 'fake-image') diff --git a/nodepool/tests/unit/test_zk.py b/nodepool/tests/unit/test_zk.py index e2f01e335..06cd6bba2 100644 --- a/nodepool/tests/unit/test_zk.py +++ b/nodepool/tests/unit/test_zk.py @@ -373,6 +373,25 @@ class TestZooKeeper(tests.DBTestCase): d = self.zk.getMostRecentImageUpload(image, provider, zk.READY) self.assertEqual(upload2.state_time, d.state_time) + def test_getProviderUploads(self): + image = "ubuntu-trusty" + provider = "rax" + + build = zk.ImageBuild() + build.state = zk.READY + bnum = self.zk.storeBuild(image, build) + + upload = zk.ImageUpload() + upload.state = zk.READY + upnum = self.zk.storeImageUpload(image, bnum, provider, upload) + + d = self.zk.getProviderUploads(provider) + self.assertIn(image, d) + self.assertIn(bnum, d[image]) + self.assertEqual(1, len(d[image][bnum])) + self.assertIsInstance(d[image][bnum][0], zk.ImageUpload) + self.assertEqual(upnum, d[image][bnum][0].id) + def test_getBuilds_any(self): image = "ubuntu-trusty" path = self.zk._imageBuildsPath(image) diff --git a/nodepool/zk.py b/nodepool/zk.py index 82b58bccf..67f07163d 100644 --- a/nodepool/zk.py +++ b/nodepool/zk.py @@ -2184,6 +2184,35 @@ class ZooKeeper(object): provider_builds[image].append(build) return provider_builds + def getProviderUploads(self, provider_name): + ''' + Get all uploads for a provider for each image. + + :param str provider_name: The provider name. + :returns: A dict, keyed by image name and build ID, of a list of + ImageUpload objects. + ''' + provider_uploads = {} + image_names = self.getImageNames() + for image in image_names: + build_numbers = self.getBuildNumbers(image) + for build in build_numbers: + # If this build is not valid for this provider, move along. + if provider_name not in self.getBuildProviders(image, build): + continue + + # We've determined that we at least have a build for this + # provider so init with an empty upload list. + if image not in provider_uploads: + provider_uploads[image] = {} + provider_uploads[image][build] = [] + + # Add any uploads we might have for this provider. + uploads = self.getUploads(image, build, provider_name) + for upload in uploads: + provider_uploads[image][build].append(upload) + return provider_uploads + def getProviderNodes(self, provider_name): ''' Get all nodes for a provider.