From 2cc996356c438e45a876a6cb51a332d070044c1e Mon Sep 17 00:00:00 2001
From: Dean Troyer <dtroyer@gmail.com>
Date: Thu, 11 Jul 2013 15:41:18 -0500
Subject: [PATCH] Add aggregate commands

* Add aggregate: add host, create, delete, list, remove host, set, show

* Add list --long option
* Filter 'availability_zone' from the metadata fields
* Rename 'metadata' column to 'properties' in all output

Bug: 1172032
Blueprint: nova-client

Change-Id: Icd408c2b34af07f5102f53d3778d8546952a12c5
---
 openstackclient/compute/v2/aggregate.py | 322 ++++++++++++++++++++++++
 setup.cfg                               |   8 +
 2 files changed, 330 insertions(+)
 create mode 100644 openstackclient/compute/v2/aggregate.py

diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
new file mode 100644
index 0000000000..d786d7e5b0
--- /dev/null
+++ b/openstackclient/compute/v2/aggregate.py
@@ -0,0 +1,322 @@
+#   Copyright 2012 OpenStack Foundation
+#   Copyright 2013 Nebula Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+#
+
+"""Compute v2 Aggregate action implementations"""
+
+import logging
+import six
+
+from cliff import command
+from cliff import lister
+from cliff import show
+
+from openstackclient.common import parseractions
+from openstackclient.common import utils
+
+
+class AddAggregateHost(show.ShowOne):
+    """Add host to aggregate"""
+
+    log = logging.getLogger(__name__ + '.AddAggregateHost')
+
+    def get_parser(self, prog_name):
+        parser = super(AddAggregateHost, self).get_parser(prog_name)
+        parser.add_argument(
+            'aggregate',
+            metavar='<aggregate>',
+            help='Name or ID of aggregate',
+        )
+        parser.add_argument(
+            'host',
+            metavar='<host>',
+            help='Host to add to aggregate',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug("take_action(%s)" % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+
+        aggregate = utils.find_resource(
+            compute_client.aggregates,
+            parsed_args.aggregate,
+        )
+        data = compute_client.aggregates.add_host(aggregate, parsed_args.host)
+
+        info = {}
+        info.update(data._info)
+        return zip(*sorted(six.iteritems(info)))
+
+
+class CreateAggregate(show.ShowOne):
+    """Create a new aggregate"""
+
+    log = logging.getLogger(__name__ + ".CreateAggregate")
+
+    def get_parser(self, prog_name):
+        parser = super(CreateAggregate, self).get_parser(prog_name)
+        parser.add_argument(
+            "name",
+            metavar="<name>",
+            help="New aggregate name",
+        )
+        parser.add_argument(
+            "--zone",
+            metavar="<availability-zone>",
+            help="Availability zone name",
+        )
+        parser.add_argument(
+            "--property",
+            metavar="<key=value>",
+            action=parseractions.KeyValueAction,
+            help='Property to add to this aggregate '
+                 '(repeat option to set multiple properties)',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug("take_action(%s)" % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+
+        info = {}
+        data = compute_client.aggregates.create(
+            parsed_args.name,
+            parsed_args.zone,
+        )
+        info.update(data._info)
+
+        if parsed_args.property:
+            info.update(compute_client.aggregates.set_metadata(
+                data,
+                parsed_args.property,
+            )._info)
+
+        return zip(*sorted(six.iteritems(info)))
+
+
+class DeleteAggregate(command.Command):
+    """Delete an existing aggregate"""
+
+    log = logging.getLogger(__name__ + '.DeleteAggregate')
+
+    def get_parser(self, prog_name):
+        parser = super(DeleteAggregate, self).get_parser(prog_name)
+        parser.add_argument(
+            'aggregate',
+            metavar='<aggregate>',
+            help='Name or ID of aggregate to delete',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug('take_action(%s)' % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+        data = utils.find_resource(
+            compute_client.aggregates,
+            parsed_args.aggregate,
+        )
+        compute_client.aggregates.delete(data.id)
+        return
+
+
+class ListAggregate(lister.Lister):
+    """List all aggregates"""
+
+    log = logging.getLogger(__name__ + ".ListAggregate")
+
+    def get_parser(self, prog_name):
+        parser = super(ListAggregate, self).get_parser(prog_name)
+        parser.add_argument(
+            '--long',
+            action='store_true',
+            default=False,
+            help='List additional fields in output')
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug("take_action(%s)" % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+
+        data = compute_client.aggregates.list()
+
+        if parsed_args.long:
+            # Remove availability_zone from metadata because Nova doesn't
+            for d in data:
+                if 'availability_zone' in d.metadata:
+                    d.metadata.pop('availability_zone')
+            # This is the easiest way to change column headers
+            column_headers = (
+                "ID",
+                "Name",
+                "Availability Zone",
+                "Properties",
+            )
+            columns = (
+                "ID",
+                "Name",
+                "Availability Zone",
+                "Metadata",
+            )
+        else:
+            column_headers = columns = (
+                "ID",
+                "Name",
+                "Availability Zone",
+            )
+
+        return (column_headers,
+                (utils.get_item_properties(
+                    s, columns,
+                ) for s in data))
+
+
+class RemoveAggregateHost(show.ShowOne):
+    """Remove host from aggregate"""
+
+    log = logging.getLogger(__name__ + '.RemoveAggregateHost')
+
+    def get_parser(self, prog_name):
+        parser = super(RemoveAggregateHost, self).get_parser(prog_name)
+        parser.add_argument(
+            'aggregate',
+            metavar='<aggregate>',
+            help='Name or ID of aggregate',
+        )
+        parser.add_argument(
+            'host',
+            metavar='<host>',
+            help='Host to remove from aggregate',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug("take_action(%s)" % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+
+        aggregate = utils.find_resource(
+            compute_client.aggregates,
+            parsed_args.aggregate,
+        )
+        data = compute_client.aggregates.remove_host(
+            aggregate,
+            parsed_args.host,
+        )
+
+        info = {}
+        info.update(data._info)
+        return zip(*sorted(six.iteritems(info)))
+
+
+class SetAggregate(show.ShowOne):
+    """Set aggregate properties"""
+
+    log = logging.getLogger(__name__ + '.SetAggregate')
+
+    def get_parser(self, prog_name):
+        parser = super(SetAggregate, self).get_parser(prog_name)
+        parser.add_argument(
+            'aggregate',
+            metavar='<aggregate>',
+            help='Name or ID of aggregate to display',
+        )
+        parser.add_argument(
+            '--name',
+            metavar='<new-name>',
+            help='New aggregate name',
+        )
+        parser.add_argument(
+            "--zone",
+            metavar="<availability-zone>",
+            help="Availability zone name",
+        )
+        parser.add_argument(
+            "--property",
+            metavar="<key=value>",
+            action=parseractions.KeyValueAction,
+            help='Property to add/change for this aggregate '
+                 '(repeat option to set multiple properties)',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug('take_action(%s)' % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+        aggregate = utils.find_resource(
+            compute_client.aggregates,
+            parsed_args.aggregate,
+        )
+
+        info = {}
+        kwargs = {}
+        if parsed_args.name:
+            kwargs['name'] = parsed_args.name
+        if parsed_args.zone:
+            kwargs['availability_zone'] = parsed_args.zone
+        if kwargs:
+            info.update(compute_client.aggregates.update(
+                aggregate,
+                kwargs
+            )._info)
+
+        if parsed_args.property:
+            info.update(compute_client.aggregates.set_metadata(
+                aggregate,
+                parsed_args.property,
+            )._info)
+
+        if info:
+            return zip(*sorted(six.iteritems(info)))
+        else:
+            return ({}, {})
+
+
+class ShowAggregate(show.ShowOne):
+    """Show a specific aggregate"""
+
+    log = logging.getLogger(__name__ + '.ShowAggregate')
+
+    def get_parser(self, prog_name):
+        parser = super(ShowAggregate, self).get_parser(prog_name)
+        parser.add_argument(
+            'aggregate',
+            metavar='<aggregate>',
+            help='Name or ID of aggregate to display',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug('take_action(%s)' % parsed_args)
+
+        compute_client = self.app.client_manager.compute
+        data = utils.find_resource(
+            compute_client.aggregates,
+            parsed_args.aggregate,
+        )
+        # Remove availability_zone from metadata because Nova doesn't
+        if 'availability_zone' in data.metadata:
+            data.metadata.pop('availability_zone')
+        # Map 'metadata' column to 'properties'
+        data._info.update({'properties': data._info.pop('metadata')})
+
+        info = {}
+        info.update(data._info)
+        return zip(*sorted(six.iteritems(info)))
diff --git a/setup.cfg b/setup.cfg
index 2068a92a5a..dade8c5885 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -154,6 +154,14 @@ openstack.compute.v2 =
     agent_list = openstackclient.compute.v2.agent:ListAgent
     agent_set = openstackclient.compute.v2.agent:SetAgent
 
+    aggregate_add_host = openstackclient.compute.v2.aggregate:AddAggregateHost
+    aggregate_create = openstackclient.compute.v2.aggregate:CreateAggregate
+    aggregate_delete = openstackclient.compute.v2.aggregate:DeleteAggregate
+    aggregate_list = openstackclient.compute.v2.aggregate:ListAggregate
+    aggregate_remove_host = openstackclient.compute.v2.aggregate:RemoveAggregateHost
+    aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate
+    aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate
+
     compute_service_list = openstackclient.compute.v2.service:ListService
     compute_service_set = openstackclient.compute.v2.service:SetService