OpenStack Image Management (Glance)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cache_manage.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. #!/usr/bin/env python
  2. # Copyright 2011 OpenStack Foundation
  3. # All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. # not use this file except in compliance with the License. You may obtain
  7. # a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. # License for the specific language governing permissions and limitations
  15. # under the License.
  16. """
  17. A simple cache management utility for Glance.
  18. """
  19. from __future__ import print_function
  20. import argparse
  21. import collections
  22. import datetime
  23. import functools
  24. import os
  25. import sys
  26. import time
  27. from oslo_utils import encodeutils
  28. import prettytable
  29. from six.moves import input
  30. # If ../glance/__init__.py exists, add ../ to Python search path, so that
  31. # it will override what happens to be installed in /usr/(local/)lib/python...
  32. possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
  33. os.pardir,
  34. os.pardir))
  35. if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
  36. sys.path.insert(0, possible_topdir)
  37. from glance.common import exception
  38. import glance.image_cache.client
  39. from glance.version import version_info as version
  40. SUCCESS = 0
  41. FAILURE = 1
  42. def catch_error(action):
  43. """Decorator to provide sensible default error handling for actions."""
  44. def wrap(func):
  45. @functools.wraps(func)
  46. def wrapper(*args, **kwargs):
  47. try:
  48. ret = func(*args, **kwargs)
  49. return SUCCESS if ret is None else ret
  50. except exception.NotFound:
  51. options = args[0]
  52. print("Cache management middleware not enabled on host %s" %
  53. options.host)
  54. return FAILURE
  55. except exception.Forbidden:
  56. print("Not authorized to make this request.")
  57. return FAILURE
  58. except Exception as e:
  59. options = args[0]
  60. if options.debug:
  61. raise
  62. print("Failed to %s. Got error:" % action)
  63. pieces = encodeutils.exception_to_unicode(e).split('\n')
  64. for piece in pieces:
  65. print(piece)
  66. return FAILURE
  67. return wrapper
  68. return wrap
  69. @catch_error('show cached images')
  70. def list_cached(args):
  71. """%(prog)s list-cached [options]
  72. List all images currently cached.
  73. """
  74. client = get_client(args)
  75. images = client.get_cached_images()
  76. if not images:
  77. print("No cached images.")
  78. return SUCCESS
  79. print("Found %d cached images..." % len(images))
  80. pretty_table = prettytable.PrettyTable(("ID",
  81. "Last Accessed (UTC)",
  82. "Last Modified (UTC)",
  83. "Size",
  84. "Hits"))
  85. pretty_table.align['Size'] = "r"
  86. pretty_table.align['Hits'] = "r"
  87. for image in images:
  88. last_accessed = image['last_accessed']
  89. if last_accessed == 0:
  90. last_accessed = "N/A"
  91. else:
  92. last_accessed = datetime.datetime.utcfromtimestamp(
  93. last_accessed).isoformat()
  94. pretty_table.add_row((
  95. image['image_id'],
  96. last_accessed,
  97. datetime.datetime.utcfromtimestamp(
  98. image['last_modified']).isoformat(),
  99. image['size'],
  100. image['hits']))
  101. print(pretty_table.get_string())
  102. return SUCCESS
  103. @catch_error('show queued images')
  104. def list_queued(args):
  105. """%(prog)s list-queued [options]
  106. List all images currently queued for caching.
  107. """
  108. client = get_client(args)
  109. images = client.get_queued_images()
  110. if not images:
  111. print("No queued images.")
  112. return SUCCESS
  113. print("Found %d queued images..." % len(images))
  114. pretty_table = prettytable.PrettyTable(("ID",))
  115. for image in images:
  116. pretty_table.add_row((image,))
  117. print(pretty_table.get_string())
  118. @catch_error('queue the specified image for caching')
  119. def queue_image(args):
  120. """%(prog)s queue-image <IMAGE_ID> [options]
  121. Queues an image for caching.
  122. """
  123. if len(args.command) == 2:
  124. image_id = args.command[1]
  125. else:
  126. print("Please specify one and only ID of the image you wish to ")
  127. print("queue from the cache as the first argument")
  128. return FAILURE
  129. if (not args.force and
  130. not user_confirm("Queue image %(image_id)s for caching?" %
  131. {'image_id': image_id}, default=False)):
  132. return SUCCESS
  133. client = get_client(args)
  134. client.queue_image_for_caching(image_id)
  135. if args.verbose:
  136. print("Queued image %(image_id)s for caching" %
  137. {'image_id': image_id})
  138. return SUCCESS
  139. @catch_error('delete the specified cached image')
  140. def delete_cached_image(args):
  141. """%(prog)s delete-cached-image <IMAGE_ID> [options]
  142. Deletes an image from the cache.
  143. """
  144. if len(args.command) == 2:
  145. image_id = args.command[1]
  146. else:
  147. print("Please specify one and only ID of the image you wish to ")
  148. print("delete from the cache as the first argument")
  149. return FAILURE
  150. if (not args.force and
  151. not user_confirm("Delete cached image %(image_id)s?" %
  152. {'image_id': image_id}, default=False)):
  153. return SUCCESS
  154. client = get_client(args)
  155. client.delete_cached_image(image_id)
  156. if args.verbose:
  157. print("Deleted cached image %(image_id)s" % {'image_id': image_id})
  158. return SUCCESS
  159. @catch_error('Delete all cached images')
  160. def delete_all_cached_images(args):
  161. """%(prog)s delete-all-cached-images [options]
  162. Remove all images from the cache.
  163. """
  164. if (not args.force and
  165. not user_confirm("Delete all cached images?", default=False)):
  166. return SUCCESS
  167. client = get_client(args)
  168. num_deleted = client.delete_all_cached_images()
  169. if args.verbose:
  170. print("Deleted %(num_deleted)s cached images" %
  171. {'num_deleted': num_deleted})
  172. return SUCCESS
  173. @catch_error('delete the specified queued image')
  174. def delete_queued_image(args):
  175. """%(prog)s delete-queued-image <IMAGE_ID> [options]
  176. Deletes an image from the cache.
  177. """
  178. if len(args.command) == 2:
  179. image_id = args.command[1]
  180. else:
  181. print("Please specify one and only ID of the image you wish to ")
  182. print("delete from the cache as the first argument")
  183. return FAILURE
  184. if (not args.force and
  185. not user_confirm("Delete queued image %(image_id)s?" %
  186. {'image_id': image_id}, default=False)):
  187. return SUCCESS
  188. client = get_client(args)
  189. client.delete_queued_image(image_id)
  190. if args.verbose:
  191. print("Deleted queued image %(image_id)s" % {'image_id': image_id})
  192. return SUCCESS
  193. @catch_error('Delete all queued images')
  194. def delete_all_queued_images(args):
  195. """%(prog)s delete-all-queued-images [options]
  196. Remove all images from the cache queue.
  197. """
  198. if (not args.force and
  199. not user_confirm("Delete all queued images?", default=False)):
  200. return SUCCESS
  201. client = get_client(args)
  202. num_deleted = client.delete_all_queued_images()
  203. if args.verbose:
  204. print("Deleted %(num_deleted)s queued images" %
  205. {'num_deleted': num_deleted})
  206. return SUCCESS
  207. def get_client(options):
  208. """Return a new client object to a Glance server.
  209. specified by the --host and --port options
  210. supplied to the CLI
  211. """
  212. return glance.image_cache.client.get_client(
  213. host=options.host,
  214. port=options.port,
  215. username=options.os_username,
  216. password=options.os_password,
  217. tenant=options.os_tenant_name,
  218. auth_url=options.os_auth_url,
  219. auth_strategy=options.os_auth_strategy,
  220. auth_token=options.os_auth_token,
  221. region=options.os_region_name,
  222. insecure=options.insecure)
  223. def env(*vars, **kwargs):
  224. """Search for the first defined of possibly many env vars.
  225. Returns the first environment variable defined in vars, or
  226. returns the default defined in kwargs.
  227. """
  228. for v in vars:
  229. value = os.environ.get(v)
  230. if value:
  231. return value
  232. return kwargs.get('default', '')
  233. def print_help(args):
  234. """
  235. Print help specific to a command
  236. """
  237. command = lookup_command(args.command[1])
  238. print(command.__doc__ % {'prog': os.path.basename(sys.argv[0])})
  239. def parse_args(parser):
  240. """Set up the CLI and config-file options that may be
  241. parsed and program commands.
  242. :param parser: The option parser
  243. """
  244. parser.add_argument('command', default='help', nargs='*',
  245. help='The command to execute')
  246. parser.add_argument('-v', '--verbose', default=False, action="store_true",
  247. help="Print more verbose output.")
  248. parser.add_argument('-d', '--debug', default=False, action="store_true",
  249. help="Print debugging output.")
  250. parser.add_argument('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
  251. help="Address of Glance API host.")
  252. parser.add_argument('-p', '--port', dest="port", metavar="PORT",
  253. type=int, default=9292,
  254. help="Port the Glance API host listens on.")
  255. parser.add_argument('-k', '--insecure', dest="insecure",
  256. default=False, action="store_true",
  257. help='Explicitly allow glance to perform "insecure" '
  258. "SSL (https) requests. The server's certificate "
  259. "will not be verified against any certificate "
  260. "authorities. This option should be used with "
  261. "caution.")
  262. parser.add_argument('-f', '--force', dest="force",
  263. default=False, action="store_true",
  264. help="Prevent select actions from requesting "
  265. "user confirmation.")
  266. parser.add_argument('--os-auth-token',
  267. dest='os_auth_token',
  268. default=env('OS_AUTH_TOKEN'),
  269. help='Defaults to env[OS_AUTH_TOKEN].')
  270. parser.add_argument('-A', '--os_auth_token', '--auth_token',
  271. dest='os_auth_token',
  272. help=argparse.SUPPRESS)
  273. parser.add_argument('--os-username',
  274. dest='os_username',
  275. default=env('OS_USERNAME'),
  276. help='Defaults to env[OS_USERNAME].')
  277. parser.add_argument('-I', '--os_username',
  278. dest='os_username',
  279. help=argparse.SUPPRESS)
  280. parser.add_argument('--os-password',
  281. dest='os_password',
  282. default=env('OS_PASSWORD'),
  283. help='Defaults to env[OS_PASSWORD].')
  284. parser.add_argument('-K', '--os_password',
  285. dest='os_password',
  286. help=argparse.SUPPRESS)
  287. parser.add_argument('--os-region-name',
  288. dest='os_region_name',
  289. default=env('OS_REGION_NAME'),
  290. help='Defaults to env[OS_REGION_NAME].')
  291. parser.add_argument('-R', '--os_region_name',
  292. dest='os_region_name',
  293. help=argparse.SUPPRESS)
  294. parser.add_argument('--os-tenant-id',
  295. dest='os_tenant_id',
  296. default=env('OS_TENANT_ID'),
  297. help='Defaults to env[OS_TENANT_ID].')
  298. parser.add_argument('--os_tenant_id',
  299. dest='os_tenant_id',
  300. help=argparse.SUPPRESS)
  301. parser.add_argument('--os-tenant-name',
  302. dest='os_tenant_name',
  303. default=env('OS_TENANT_NAME'),
  304. help='Defaults to env[OS_TENANT_NAME].')
  305. parser.add_argument('-T', '--os_tenant_name',
  306. dest='os_tenant_name',
  307. help=argparse.SUPPRESS)
  308. parser.add_argument('--os-auth-url',
  309. default=env('OS_AUTH_URL'),
  310. help='Defaults to env[OS_AUTH_URL].')
  311. parser.add_argument('-N', '--os_auth_url',
  312. dest='os_auth_url',
  313. help=argparse.SUPPRESS)
  314. parser.add_argument('-S', '--os_auth_strategy', dest="os_auth_strategy",
  315. metavar="STRATEGY",
  316. help="Authentication strategy (keystone or noauth).")
  317. version_string = version.cached_version_string()
  318. parser.add_argument('--version', action='version',
  319. version=version_string)
  320. return parser.parse_args()
  321. CACHE_COMMANDS = collections.OrderedDict()
  322. CACHE_COMMANDS['help'] = (
  323. print_help, 'Output help for one of the commands below')
  324. CACHE_COMMANDS['list-cached'] = (
  325. list_cached, 'List all images currently cached')
  326. CACHE_COMMANDS['list-queued'] = (
  327. list_queued, 'List all images currently queued for caching')
  328. CACHE_COMMANDS['queue-image'] = (
  329. queue_image, 'Queue an image for caching')
  330. CACHE_COMMANDS['delete-cached-image'] = (
  331. delete_cached_image, 'Purges an image from the cache')
  332. CACHE_COMMANDS['delete-all-cached-images'] = (
  333. delete_all_cached_images, 'Removes all images from the cache')
  334. CACHE_COMMANDS['delete-queued-image'] = (
  335. delete_queued_image, 'Deletes an image from the cache queue')
  336. CACHE_COMMANDS['delete-all-queued-images'] = (
  337. delete_all_queued_images, 'Deletes all images from the cache queue')
  338. def _format_command_help():
  339. """Formats the help string for subcommands."""
  340. help_msg = "Commands:\n\n"
  341. for command, info in CACHE_COMMANDS.items():
  342. if command == 'help':
  343. command = 'help <command>'
  344. help_msg += " %-28s%s\n\n" % (command, info[1])
  345. return help_msg
  346. def lookup_command(command_name):
  347. try:
  348. command = CACHE_COMMANDS[command_name]
  349. return command[0]
  350. except KeyError:
  351. print('\nError: "%s" is not a valid command.\n' % command_name)
  352. print(_format_command_help())
  353. sys.exit("Unknown command: %(cmd_name)s" % {'cmd_name': command_name})
  354. def user_confirm(prompt, default=False):
  355. """Yes/No question dialog with user.
  356. :param prompt: question/statement to present to user (string)
  357. :param default: boolean value to return if empty string
  358. is received as response to prompt
  359. """
  360. if default:
  361. prompt_default = "[Y/n]"
  362. else:
  363. prompt_default = "[y/N]"
  364. answer = input("%s %s " % (prompt, prompt_default))
  365. if answer == "":
  366. return default
  367. else:
  368. return answer.lower() in ("yes", "y")
  369. def main():
  370. parser = argparse.ArgumentParser(
  371. description=_format_command_help(),
  372. formatter_class=argparse.RawDescriptionHelpFormatter)
  373. args = parse_args(parser)
  374. if args.command[0] == 'help' and len(args.command) == 1:
  375. parser.print_help()
  376. return
  377. # Look up the command to run
  378. command = lookup_command(args.command[0])
  379. try:
  380. start_time = time.time()
  381. result = command(args)
  382. end_time = time.time()
  383. if args.verbose:
  384. print("Completed in %-0.4f sec." % (end_time - start_time))
  385. sys.exit(result)
  386. except (RuntimeError, NotImplementedError) as e:
  387. sys.exit("ERROR: %s" % e)
  388. if __name__ == '__main__':
  389. main()