Add openstack image list and cleanup cmds 47/4047/7
authorThanh Ha <thanh.ha@linuxfoundation.org>
Fri, 10 Mar 2017 02:51:46 +0000 (21:51 -0500)
committerThanh Ha <thanh.ha@linuxfoundation.org>
Mon, 13 Mar 2017 20:55:23 +0000 (16:55 -0400)
This adds 2 new commands to lftools to manipulate openstack images:

    list:     Prints available images in the cloud.
    cleanup:  Removes old unused images.

Also fix ctx not getting passed properly to the cli function.

Change-Id: I5c73964f33f4fccb56684910fb08828c747fa562
Signed-off-by: Thanh Ha <thanh.ha@linuxfoundation.org>
docs/commands/index.rst
docs/commands/openstack.rst [new file with mode: 0644]
lftools/cli/__init__.py
lftools/openstack/__init__.py [new file with mode: 0644]
lftools/openstack/cmd.py [new file with mode: 0644]
lftools/openstack/image.py [new file with mode: 0644]
requirements.txt
setup.py

index f0d0179..5499452 100644 (file)
@@ -8,4 +8,6 @@ bash. It supports the following commands.
 .. toctree::
     :maxdepth: 2
 
+    nexus
+    openstack
     version
diff --git a/docs/commands/openstack.rst b/docs/commands/openstack.rst
new file mode 100644 (file)
index 0000000..ddf3279
--- /dev/null
@@ -0,0 +1,31 @@
+*********
+OpenStack
+*********
+
+.. program-output:: lftools openstack --help
+
+Commands
+========
+
+.. contents:: OpenStack Commands
+    :local:
+
+image
+-----
+
+.. program-output:: lftools openstack --os-cloud docs image --help
+
+cleanup
+^^^^^^^
+
+The intent of this command is to automatically cleanup old images in the cloud.
+The OpenDaylight project has 2 clouds, a Private Cloud and a Public cloud which
+needs the `--clouds` option to automatically remove the same images from
+multiple clouds simultaniously.
+
+.. program-output:: lftools openstack --os-cloud docs image cleanup --help
+
+list
+^^^^
+
+.. program-output:: lftools openstack --os-cloud docs image list --help
index b36abde..69c84ad 100644 (file)
@@ -15,6 +15,7 @@ __author__ = 'Thanh Ha'
 import click
 from lftools.cli.nexus import nexus
 from lftools.cli.version import version
+from lftools.openstack.cmd import openstack
 
 
 @click.group()
@@ -25,9 +26,15 @@ def cli(ctx):
     pass
 
 
+cli.add_command(openstack)
 cli.add_command(nexus)
 cli.add_command(version)
 
 
-if __name__ == '__main__':
+def main():
+    """Entry point for lftools CLI."""
     cli(obj={})
+
+
+if __name__ == '__main__':
+    main()
diff --git a/lftools/openstack/__init__.py b/lftools/openstack/__init__.py
new file mode 100644 (file)
index 0000000..afb60d8
--- /dev/null
@@ -0,0 +1,12 @@
+# @License EPL-1.0 <http://spdx.org/licenses/EPL-1.0>
+##############################################################################
+# Copyright (c) 2017 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+"""lftools openstack package."""
+
+__author__ = 'Thanh Ha'
diff --git a/lftools/openstack/cmd.py b/lftools/openstack/cmd.py
new file mode 100644 (file)
index 0000000..c693da0
--- /dev/null
@@ -0,0 +1,73 @@
+# -*- code: utf-8 -*-
+# @License EPL-1.0 <http://spdx.org/licenses/EPL-1.0>
+##############################################################################
+# Copyright (c) 2017 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+"""CLI configuration for openstack command."""
+
+__author__ = 'Thanh Ha'
+
+
+import click
+from lftools.openstack import image as os_image
+
+
+@click.group()
+@click.option('--os-cloud', envvar='OS_CLOUD', type=str, required=True)
+@click.pass_context
+def openstack(ctx, os_cloud):
+    """Provide an interface to OpenStack."""
+    ctx.obj['os_cloud'] = os_cloud
+
+
+@openstack.group()
+@click.pass_context
+def image(ctx):
+    """Command for manipulating images."""
+    pass
+
+
+@click.command()
+@click.option(
+    '--days', type=int, default=0,
+    help='Find images older than or equal to days.')
+@click.option(
+    '--hide-public', type=bool, default=False,
+    help='Ignore public images.')
+@click.option(
+    '--clouds', type=str, default=None,
+    help=('Clouds (as defined in clouds.yaml) to remove images from. If not'
+          'passed will assume from os-cloud parameter. (optional)'))
+@click.pass_context
+def cleanup(ctx, days, hide_public, clouds):
+    """Cleanup old images."""
+    os_image.cleanup(
+        ctx.obj['os_cloud'],
+        days=days,
+        hide_public=hide_public,
+        clouds=clouds)
+
+
+@click.command()
+@click.option(
+    '--days', type=int, default=0,
+    help='Find images older than or equal to days.')
+@click.option(
+    '--hide-public', type=bool, default=False,
+    help='Ignore public images.')
+@click.pass_context
+def list(ctx, days, hide_public):
+    """List cloud images."""
+    os_image.list(
+        ctx.obj['os_cloud'],
+        days=days,
+        hide_public=hide_public)
+
+
+image.add_command(cleanup)
+image.add_command(list)
diff --git a/lftools/openstack/image.py b/lftools/openstack/image.py
new file mode 100644 (file)
index 0000000..1e99ac1
--- /dev/null
@@ -0,0 +1,90 @@
+# -*- code: utf-8 -*-
+# @License EPL-1.0 <http://spdx.org/licenses/EPL-1.0>
+##############################################################################
+# Copyright (c) 2017 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+"""Image related sub-commands for openstack command."""
+
+__author__ = 'Thanh Ha'
+
+from datetime import datetime
+from datetime import timedelta
+
+import shade
+
+
+def _filter_images(images, days=0, hide_public=False):
+    """Filter image data and return list.
+
+    :arg bool hide_public: Whether or not to include public images.
+    """
+    filtered = []
+    for image in images:
+        if hide_public and image.is_public:
+            continue
+        if days and (
+                datetime.strptime(image.created_at, '%Y-%m-%dT%H:%M:%SZ')
+                >= datetime.now() - timedelta(days=days)):
+            continue
+
+        filtered.append(image)
+    return filtered
+
+
+def list(os_cloud, days=0, hide_public=False):
+    """List images found according to parameters."""
+    cloud = shade.openstack_cloud(cloud=os_cloud)
+    images = cloud.list_images()
+
+    filtered_images = _filter_images(images, days, hide_public)
+    for image in filtered_images:
+        print(image.name)
+
+
+def cleanup(os_cloud, days=0, hide_public=False, clouds=None):
+    """Remove image from cloud.
+
+    :arg str os_cloud: Cloud name as defined in OpenStack clouds.yaml.
+    :arg int days: Filter images that are older than number of days.
+    :arg bool hide_public: If true, will ignore public images. (Default: false)
+    :arg str clouds: If passed, comma-separated list of clouds to remove image
+        from. Otherwise os_cloud will be used.
+    """
+    def _remove_images_from_cloud(images, cloud):
+        print('Removing {} images from {}.'.format(len(images), cloud.cloud_config.name))
+        for image in images:
+            try:
+                result = cloud.delete_image(image.name)
+            except shade.exc.OpenStackCloudException as e:
+                if str(e).startswith('Multiple matches found for'):
+                    print('WARNING: {}. Skipping image...'.format(str(e)))
+                    continue
+                else:
+                    print('ERROR: Unexpected exception: {}'.format(str(e)))
+                    raise
+
+            if not result:
+                print('WARNING: Failed to remove \"{}\" from {}. Possibly already deleted.'
+                      .format(image.name, cloud.cloud_config.name))
+            else:
+                print('Removed "{}" from {}.'.format(image.name, cloud.cloud_config.name))
+
+    cloud = shade.openstack_cloud(cloud=os_cloud)
+    if clouds:
+        cloud_list = []
+        for c in clouds.split(","):
+            cloud_list.append(shade.openstack_cloud(cloud=c))
+
+    images = cloud.list_images()
+    filtered_images = _filter_images(images, days, hide_public)
+
+    if clouds:
+        for c in cloud_list:
+            _remove_images_from_cloud(filtered_images, c)
+    else:
+        _remove_images_from_cloud(filtered_images, cloud)
index 6bfbd0d..8ddebbf 100644 (file)
@@ -1,6 +1,7 @@
 click
 pyyaml
 requests
+shade
 sphinx>=1.4.9
 sphinxcontrib-programoutput
 sphinx_bootstrap_theme>=0.4.14
index 0e7c601..2f8d4c4 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@ setup(
     tests_require=['pytest'],
     entry_points='''
         [console_scripts]
-        lftools=lftools.cli:cli
+        lftools=lftools.cli:main
     ''',
     scripts=[
         'shell/version',