Add provider info command
This command will display all ZooKeeper data for a given provider, and provide and option to remove all of the data from ZooKeeper. This can be useful when an operator must permanently remove a pre-existing provider from nodepool and cannot cleanly shutdown the services otherwise. Example: nodepool info rax nodepool info --erase rax Change-Id: I527aae5ff89aac864f984af050abb83e7bc3ac04
This commit is contained in:
parent
63dbab87df
commit
742b0b1d6b
|
@ -203,6 +203,13 @@ delete
|
||||||
.. program-output:: nodepool delete --help
|
.. program-output:: nodepool delete --help
|
||||||
:nostderr:
|
:nostderr:
|
||||||
|
|
||||||
|
The following subcommands deal with ZooKeeper data management:
|
||||||
|
|
||||||
|
info
|
||||||
|
^^^^
|
||||||
|
.. program-output:: nodepool info --help
|
||||||
|
:nostderr:
|
||||||
|
|
||||||
If Nodepool's database gets out of sync with reality, the following
|
If Nodepool's database gets out of sync with reality, the following
|
||||||
commands can help identify compute instances or images that are
|
commands can help identify compute instances or images that are
|
||||||
unknown to Nodepool:
|
unknown to Nodepool:
|
||||||
|
|
|
@ -122,6 +122,23 @@ class NodePoolCmd(NodepoolApp):
|
||||||
help='list the current node requests')
|
help='list the current node requests')
|
||||||
cmd_request_list.set_defaults(func=self.request_list)
|
cmd_request_list.set_defaults(func=self.request_list)
|
||||||
|
|
||||||
|
cmd_info = subparsers.add_parser(
|
||||||
|
'info',
|
||||||
|
help='Show provider data from zookeeper')
|
||||||
|
cmd_info.add_argument(
|
||||||
|
'provider',
|
||||||
|
help='provider name',
|
||||||
|
metavar='PROVIDER')
|
||||||
|
cmd_info.add_argument(
|
||||||
|
'--erase',
|
||||||
|
help='erase ZooKeeper data for this provider',
|
||||||
|
action='store_true')
|
||||||
|
cmd_info.add_argument(
|
||||||
|
'--force',
|
||||||
|
help='used with --erase to bypass the warning prompt',
|
||||||
|
action='store_true')
|
||||||
|
cmd_info.set_defaults(func=self.info)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def setup_logging(self):
|
def setup_logging(self):
|
||||||
|
@ -285,6 +302,46 @@ class NodePoolCmd(NodepoolApp):
|
||||||
self.zk.storeImageUpload(image.image_name, image.build_id,
|
self.zk.storeImageUpload(image.image_name, image.build_id,
|
||||||
image.provider_name, image, image.id)
|
image.provider_name, image, image.id)
|
||||||
|
|
||||||
|
def erase(self, provider_name, provider_builds, provider_nodes):
|
||||||
|
print("\nErasing build data for %s..." % provider_name)
|
||||||
|
self.zk.removeProviderBuilds(provider_name, provider_builds)
|
||||||
|
print("Erasing node data for %s..." % provider_name)
|
||||||
|
self.zk.removeProviderNodes(provider_name, provider_nodes)
|
||||||
|
|
||||||
|
def info(self):
|
||||||
|
provider_name = self.args.provider
|
||||||
|
provider_builds = self.zk.getProviderBuilds(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'])
|
||||||
|
t.align = 'l'
|
||||||
|
for image, builds in provider_builds.items():
|
||||||
|
t.add_row([image, ','.join(builds)])
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
print("\nNodes:")
|
||||||
|
t = PrettyTable(['ID', 'Server ID'])
|
||||||
|
t.align = 'l'
|
||||||
|
for node in provider_nodes:
|
||||||
|
t.add_row([node.id, node.external_id])
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
if self.args.erase:
|
||||||
|
if self.args.force:
|
||||||
|
self.erase(provider_name, provider_builds, provider_nodes)
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nWARNING! This action is not reversible!")
|
||||||
|
answer = input("Erase ZooKeeper data for provider %s? [N/y] " %
|
||||||
|
provider_name)
|
||||||
|
if answer.lower() != 'y':
|
||||||
|
print("Aborting. No data erased.")
|
||||||
|
else:
|
||||||
|
self.erase(provider_name, provider_builds, provider_nodes)
|
||||||
|
|
||||||
def config_validate(self):
|
def config_validate(self):
|
||||||
validator = ConfigValidator(self.args.config)
|
validator = ConfigValidator(self.args.config)
|
||||||
validator.validate()
|
validator.validate()
|
||||||
|
@ -318,7 +375,7 @@ class NodePoolCmd(NodepoolApp):
|
||||||
'image-list', 'dib-image-delete',
|
'image-list', 'dib-image-delete',
|
||||||
'image-delete', 'alien-image-list',
|
'image-delete', 'alien-image-list',
|
||||||
'list', 'hold', 'delete',
|
'list', 'hold', 'delete',
|
||||||
'request-list'):
|
'request-list', 'info'):
|
||||||
self.zk = zk.ZooKeeper()
|
self.zk = zk.ZooKeeper()
|
||||||
self.zk.connect(list(config.zookeeper_servers.values()))
|
self.zk.connect(list(config.zookeeper_servers.values()))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
elements-dir: .
|
||||||
|
images-dir: '{images_dir}'
|
||||||
|
|
||||||
|
zookeeper-servers:
|
||||||
|
- host: {zookeeper_host}
|
||||||
|
port: {zookeeper_port}
|
||||||
|
chroot: {zookeeper_chroot}
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- name: fake-label
|
||||||
|
min-ready: 1
|
||||||
|
- name: fake-label2
|
||||||
|
min-ready: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: fake-provider
|
||||||
|
cloud: fake
|
||||||
|
driver: fake
|
||||||
|
region-name: fake-region
|
||||||
|
rate: 0.0001
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
pools:
|
||||||
|
- name: main
|
||||||
|
max-servers: 96
|
||||||
|
labels:
|
||||||
|
- name: fake-label
|
||||||
|
diskimage: fake-image
|
||||||
|
min-ram: 8192
|
||||||
|
|
||||||
|
- name: fake-provider2
|
||||||
|
cloud: fake
|
||||||
|
driver: fake
|
||||||
|
region-name: fake-region
|
||||||
|
rate: 0.0001
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
pools:
|
||||||
|
- name: main
|
||||||
|
max-servers: 96
|
||||||
|
labels:
|
||||||
|
- name: fake-label2
|
||||||
|
diskimage: fake-image
|
||||||
|
min-ram: 8192
|
||||||
|
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
elements:
|
||||||
|
- fedora
|
||||||
|
- vm
|
||||||
|
release: 21
|
||||||
|
env-vars:
|
||||||
|
TMPDIR: /opt/dib_tmp
|
||||||
|
DIB_IMAGE_CACHE: /opt/dib_cache
|
||||||
|
DIB_CLOUD_IMAGES: http://download.fedoraproject.org/pub/fedora/linux/releases/test/21-Beta/Cloud/Images/x86_64/
|
||||||
|
BASE_IMAGE_FILE: Fedora-Cloud-Base-20141029-21_Beta.x86_64.qcow2
|
|
@ -0,0 +1,39 @@
|
||||||
|
elements-dir: .
|
||||||
|
images-dir: '{images_dir}'
|
||||||
|
|
||||||
|
zookeeper-servers:
|
||||||
|
- host: {zookeeper_host}
|
||||||
|
port: {zookeeper_port}
|
||||||
|
chroot: {zookeeper_chroot}
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- name: fake-label
|
||||||
|
min-ready: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: fake-provider
|
||||||
|
cloud: fake
|
||||||
|
driver: fake
|
||||||
|
region-name: fake-region
|
||||||
|
rate: 0.0001
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
pools:
|
||||||
|
- name: main
|
||||||
|
max-servers: 96
|
||||||
|
labels:
|
||||||
|
- name: fake-label
|
||||||
|
diskimage: fake-image
|
||||||
|
min-ram: 8192
|
||||||
|
|
||||||
|
diskimages:
|
||||||
|
- name: fake-image
|
||||||
|
elements:
|
||||||
|
- fedora
|
||||||
|
- vm
|
||||||
|
release: 21
|
||||||
|
env-vars:
|
||||||
|
TMPDIR: /opt/dib_tmp
|
||||||
|
DIB_IMAGE_CACHE: /opt/dib_cache
|
||||||
|
DIB_CLOUD_IMAGES: http://download.fedoraproject.org/pub/fedora/linux/releases/test/21-Beta/Cloud/Images/x86_64/
|
||||||
|
BASE_IMAGE_FILE: Fedora-Cloud-Base-20141029-21_Beta.x86_64.qcow2
|
|
@ -300,3 +300,49 @@ class TestNodepoolCMD(tests.DBTestCase):
|
||||||
self.patch_argv("-c", configfile)
|
self.patch_argv("-c", configfile)
|
||||||
result = nodepoolcmd.main()
|
result = nodepoolcmd.main()
|
||||||
self.assertEqual(1, result)
|
self.assertEqual(1, result)
|
||||||
|
|
||||||
|
def test_info(self):
|
||||||
|
configfile = self.setup_config('info_cmd_two_provider.yaml')
|
||||||
|
pool = self.useNodepool(configfile, watermark_sleep=1)
|
||||||
|
self.useBuilder(configfile)
|
||||||
|
pool.start()
|
||||||
|
p1_image = self.waitForImage('fake-provider', 'fake-image')
|
||||||
|
p1_nodes = self.waitForNodes('fake-label')
|
||||||
|
p2_nodes = self.waitForNodes('fake-label2')
|
||||||
|
|
||||||
|
# Get rid of the second provider so that when we remove its
|
||||||
|
# data from ZooKeeper, the builder and launcher don't attempt to
|
||||||
|
# recreate the data.
|
||||||
|
self.replace_config(configfile, 'info_cmd_two_provider_remove.yaml')
|
||||||
|
|
||||||
|
# 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. We go ahead
|
||||||
|
# and erase the data here (after it has been displayed) so that
|
||||||
|
# we can verify the erase in the next steps.
|
||||||
|
self.assert_listed(
|
||||||
|
configfile,
|
||||||
|
['info', 'fake-provider2', '--erase', '--force'],
|
||||||
|
0, p2_nodes[0].id, 1)
|
||||||
|
|
||||||
|
# Verify that no build or node for the second provider is listed
|
||||||
|
# after the previous erase
|
||||||
|
self.assert_listed(
|
||||||
|
configfile,
|
||||||
|
['info', 'fake-provider2'],
|
||||||
|
0, 'fake-image', 0)
|
||||||
|
self.assert_listed(
|
||||||
|
configfile,
|
||||||
|
['info', 'fake-provider2'],
|
||||||
|
0, p2_nodes[0].id, 0)
|
||||||
|
|
||||||
|
# Verify that we did not affect the first provider
|
||||||
|
image = self.waitForImage('fake-provider', 'fake-image')
|
||||||
|
self.assertEqual(p1_image, image)
|
||||||
|
nodes = self.waitForNodes('fake-label')
|
||||||
|
self.assertEqual(1, len(nodes))
|
||||||
|
self.assertEqual(p1_nodes[0], nodes[0])
|
||||||
|
|
|
@ -1679,3 +1679,61 @@ class ZooKeeper(object):
|
||||||
if node.provider == provider_name and node.pool == pool_name:
|
if node.provider == provider_name and node.pool == pool_name:
|
||||||
count = count + 1
|
count = count + 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
def getProviderBuilds(self, provider_name):
|
||||||
|
'''
|
||||||
|
Get all builds for a provider for each image.
|
||||||
|
|
||||||
|
:param str provider_name: The provider name.
|
||||||
|
:returns: A dict of lists of build IDs, keyed by image name.
|
||||||
|
'''
|
||||||
|
provider_builds = {}
|
||||||
|
image_names = self.getImageNames()
|
||||||
|
for image in image_names:
|
||||||
|
build_numbers = self.getBuildNumbers(image)
|
||||||
|
for build in build_numbers:
|
||||||
|
providers = self.getBuildProviders(image, build)
|
||||||
|
for p in providers:
|
||||||
|
if p == provider_name:
|
||||||
|
if image not in provider_builds:
|
||||||
|
provider_builds[image] = []
|
||||||
|
provider_builds[image].append(build)
|
||||||
|
return provider_builds
|
||||||
|
|
||||||
|
def getProviderNodes(self, provider_name):
|
||||||
|
'''
|
||||||
|
Get all nodes for a provider.
|
||||||
|
|
||||||
|
:param str provider_name: The provider name.
|
||||||
|
:returns: A list of Node objects.
|
||||||
|
'''
|
||||||
|
provider_nodes = []
|
||||||
|
for node in self.nodeIterator():
|
||||||
|
if node.provider == provider_name:
|
||||||
|
provider_nodes.append(node)
|
||||||
|
return provider_nodes
|
||||||
|
|
||||||
|
def removeProviderBuilds(self, provider_name, provider_builds):
|
||||||
|
'''
|
||||||
|
Remove ZooKeeper build data for a provider.
|
||||||
|
|
||||||
|
:param str provider_name: The provider name.
|
||||||
|
:param dict provider_builds: Data as returned by getProviderBuilds().
|
||||||
|
'''
|
||||||
|
for image, builds in provider_builds.items():
|
||||||
|
for build in builds:
|
||||||
|
path = self._imageProviderPath(image, build)
|
||||||
|
path = "%s/%s" % (path, provider_name)
|
||||||
|
try:
|
||||||
|
self.client.delete(path, recursive=True)
|
||||||
|
except kze.NoNodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def removeProviderNodes(self, provider_name, provider_nodes):
|
||||||
|
'''
|
||||||
|
Remove ZooKeeper node data for a provider.
|
||||||
|
:param str provider_name: The provider name.
|
||||||
|
:param dict provider_nodes: Data as returned by getProviderNodes().
|
||||||
|
'''
|
||||||
|
for node in provider_nodes:
|
||||||
|
self.deleteNode(node)
|
||||||
|
|
Loading…
Reference in New Issue