From c03b9a871c4fe6b99221cb4b0d1e0eb7c90283fe Mon Sep 17 00:00:00 2001
From: Rui Chen <chenrui.momo@gmail.com>
Date: Thu, 2 Mar 2017 17:28:45 +0800
Subject: [PATCH] Add server event list and show commands

OSC server event is similar to nova's instance action commands.

Server event is the event record that had been done on a server,
include: event type(create, delete, reboot and so on),
event result(success, error), start time, finish time and so on.
These are important information for server maintains.

Change-Id: I8111091f46a0d2755728d8f9d43cc0dfe8842d13
Closes-Bug: #1642030
---
 doc/source/command-objects/server-event.rst   |  45 +++++
 doc/source/commands.rst                       |   1 +
 openstackclient/compute/v2/server_event.py    | 117 ++++++++++++
 .../compute/v2/test_server_event.py           |  97 ++++++++++
 .../tests/unit/compute/v2/fakes.py            |  44 +++++
 .../unit/compute/v2/test_server_event.py      | 167 ++++++++++++++++++
 .../notes/bug-1642030-166b2b28c8adf22e.yaml   |  11 ++
 setup.cfg                                     |   3 +
 8 files changed, 485 insertions(+)
 create mode 100644 doc/source/command-objects/server-event.rst
 create mode 100644 openstackclient/compute/v2/server_event.py
 create mode 100644 openstackclient/tests/functional/compute/v2/test_server_event.py
 create mode 100644 openstackclient/tests/unit/compute/v2/test_server_event.py
 create mode 100644 releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml

diff --git a/doc/source/command-objects/server-event.rst b/doc/source/command-objects/server-event.rst
new file mode 100644
index 0000000000..ef4685f8d6
--- /dev/null
+++ b/doc/source/command-objects/server-event.rst
@@ -0,0 +1,45 @@
+============
+server event
+============
+
+Server event is the event record that had been done on a server, include: event
+type(create, delete, reboot and so on), event result(success, error), start
+time, finish time and so on. These are important information for server
+maintains.
+
+Compute v2
+
+server event list
+-----------------
+
+List recent events of a server
+
+.. program:: server event list
+.. code:: bash
+
+    openstack server event list
+        <server>
+
+.. describe:: <server>
+
+    Server to list events (name or ID)
+
+server event show
+-----------------
+
+Show server event details
+
+.. program:: server event show
+.. code:: bash
+
+    openstack server event show
+        <server>
+        <request-id>
+
+.. describe:: <server>
+
+    Server to show event details (name or ID)
+
+.. describe:: <request-id>
+
+     Request ID of the event to show (ID only)
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index a0c67cd4c8..f423618892 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -137,6 +137,7 @@ referring to both Compute and Volume quotas.
 * ``server``: (**Compute**) virtual machine instance
 * ``server backup``: (**Compute**) backup server disk image by using snapshot method
 * ``server dump``: (**Compute**) a dump file of a server created by features like kdump
+* ``server event``: (**Compute**) events of a server
 * ``server group``: (**Compute**) a grouping of servers
 * ``server image``: (**Compute**) saved server disk image
 * ``service``: (**Identity**) a cloud service
diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py
new file mode 100644
index 0000000000..ccb19ef722
--- /dev/null
+++ b/openstackclient/compute/v2/server_event.py
@@ -0,0 +1,117 @@
+#   Copyright 2017 Huawei, Inc. All rights reserved.
+#
+#   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 Server operation event implementations"""
+
+import logging
+import six
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ListServerEvent(command.Lister):
+    _description = _("List recent events of a server")
+
+    def get_parser(self, prog_name):
+        parser = super(ListServerEvent, self).get_parser(prog_name)
+        parser.add_argument(
+            'server',
+            metavar='<server>',
+            help=_('Server to list events (name or ID)'),
+        )
+        parser.add_argument(
+            '--long',
+            action='store_true',
+            default=False,
+            help=_("List additional fields in output")
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        compute_client = self.app.client_manager.compute
+        server_id = utils.find_resource(compute_client.servers,
+                                        parsed_args.server).id
+        data = compute_client.instance_action.list(server_id)
+
+        if parsed_args.long:
+            columns = (
+                'request_id',
+                'instance_uuid',
+                'action',
+                'start_time',
+                'message',
+                'project_id',
+                'user_id',
+            )
+            column_headers = (
+                'Request ID',
+                'Server ID',
+                'Action',
+                'Start Time',
+                'Message',
+                'Project ID',
+                'User ID',
+            )
+        else:
+            columns = (
+                'request_id',
+                'instance_uuid',
+                'action',
+                'start_time',
+            )
+            column_headers = (
+                'Request ID',
+                'Server ID',
+                'Action',
+                'Start Time',
+            )
+
+        return (column_headers,
+                (utils.get_item_properties(
+                    s, columns,
+                ) for s in data))
+
+
+class ShowServerEvent(command.ShowOne):
+    _description = _("Show server event details")
+
+    def get_parser(self, prog_name):
+        parser = super(ShowServerEvent, self).get_parser(prog_name)
+        parser.add_argument(
+            'server',
+            metavar='<server>',
+            help=_('Server to show event details (name or ID)'),
+        )
+        parser.add_argument(
+            'request_id',
+            metavar='<request-id>',
+            help=_('Request ID of the event to show (ID only)'),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        compute_client = self.app.client_manager.compute
+        server_id = utils.find_resource(compute_client.servers,
+                                        parsed_args.server).id
+        action_detail = compute_client.instance_action.get(
+            server_id, parsed_args.request_id)
+
+        return zip(*sorted(six.iteritems(action_detail._info)))
diff --git a/openstackclient/tests/functional/compute/v2/test_server_event.py b/openstackclient/tests/functional/compute/v2/test_server_event.py
new file mode 100644
index 0000000000..6be5822f98
--- /dev/null
+++ b/openstackclient/tests/functional/compute/v2/test_server_event.py
@@ -0,0 +1,97 @@
+#   Copyright 2017 Huawei, Inc. All rights reserved.
+#
+#   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.
+#
+
+import json
+import uuid
+
+from openstackclient.tests.functional import base
+from openstackclient.tests.functional.compute.v2 import test_server
+
+
+class ServerEventTests(base.TestCase):
+    """Functional tests for server event."""
+
+    def setUp(self):
+        super(ServerEventTests, self).setUp()
+        _flavor = test_server.ServerTests.get_flavor()
+        _image = test_server.ServerTests.get_image()
+        _network = test_server.ServerTests.get_network()
+        self.server_name = uuid.uuid4().hex
+        cmd_output = json.loads(self.openstack(
+            'server create -f json ' +
+            '--flavor ' + _flavor + ' ' +
+            '--image ' + _image + ' ' +
+            _network + ' ' +
+            '--wait ' +
+            self.server_name
+        ))
+        if not cmd_output:
+            self.fail('Server has not been created!')
+        self.addCleanup(self.openstack, 'server delete ' + self.server_name)
+        self.assertEqual(self.server_name, cmd_output['name'])
+        self.server_id = cmd_output.get('id')
+
+    def test_server_event_list_and_show(self):
+        """Test list, show server event"""
+        # Test 'server event list' for creating
+        cmd_output = json.loads(self.openstack(
+            'server event list -f json ' + self.server_name
+        ))
+        request_id = None
+        for each_event in cmd_output:
+            self.assertNotIn('Message', each_event)
+            self.assertNotIn('Project ID', each_event)
+            self.assertNotIn('User ID', each_event)
+            if each_event.get('Action') == 'create':
+                self.assertEqual(self.server_id, each_event.get('Server ID'))
+                request_id = each_event.get('Request ID')
+                break
+        self.assertIsNotNone(request_id)
+        # Test 'server event show' for creating
+        cmd_output = json.loads(self.openstack(
+            'server event show -f json ' + self.server_name + ' ' + request_id
+        ))
+        self.assertEqual(self.server_id, cmd_output.get('instance_uuid'))
+        self.assertEqual(request_id, cmd_output.get('request_id'))
+        self.assertEqual('create', cmd_output.get('action'))
+        self.assertIsNotNone(cmd_output.get('events'))
+        self.assertIsInstance(cmd_output.get('events'), list)
+
+        # Reboot server, trigger reboot event
+        self.openstack('server reboot --wait ' + self.server_name)
+        # Test 'server event list --long' for rebooting
+        cmd_output = json.loads(self.openstack(
+            'server event list --long -f json ' + self.server_name
+        ))
+        request_id = None
+        for each_event in cmd_output:
+            self.assertIn('Message', each_event)
+            self.assertIn('Project ID', each_event)
+            self.assertIn('User ID', each_event)
+            if each_event.get('Action') == 'reboot':
+                request_id = each_event.get('Request ID')
+                self.assertEqual(self.server_id, each_event.get('Server ID'))
+                break
+        self.assertIsNotNone(request_id)
+        # Test 'server event show' for rebooting
+        cmd_output = json.loads(self.openstack(
+            'server event show -f json ' + self.server_name + ' ' + request_id
+        ))
+
+        self.assertEqual(self.server_id, cmd_output.get('instance_uuid'))
+        self.assertEqual(request_id, cmd_output.get('request_id'))
+        self.assertEqual('reboot', cmd_output.get('action'))
+        self.assertIsNotNone(cmd_output.get('events'))
+        self.assertIsInstance(cmd_output.get('events'), list)
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index 4fe735b6b4..bbb770bbba 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -204,6 +204,9 @@ class FakeComputev2Client(object):
         self.server_groups = mock.Mock()
         self.server_groups.resource_class = fakes.FakeResource(None, {})
 
+        self.instance_action = mock.Mock()
+        self.instance_action.resource_class = fakes.FakeResource(None, {})
+
         self.auth_token = kwargs['token']
 
         self.management_url = kwargs['endpoint']
@@ -656,6 +659,47 @@ class FakeServer(object):
         return mock.Mock(side_effect=servers)
 
 
+class FakeServerEvent(object):
+    """Fake one or more server event."""
+
+    @staticmethod
+    def create_one_server_event(attrs=None):
+        """Create a fake server event.
+
+        :param attrs:
+            A dictionary with all attributes
+        :return:
+            A FakeResource object, with id and other attributes
+        """
+        attrs = attrs or {}
+
+        # Set default attributes
+        server_event_info = {
+            "instance_uuid": "server-event-" + uuid.uuid4().hex,
+            "user_id": "user-id-" + uuid.uuid4().hex,
+            "start_time": "2017-02-27T07:47:13.000000",
+            "request_id": "req-" + uuid.uuid4().hex,
+            "action": "create",
+            "message": None,
+            "project_id": "project-id-" + uuid.uuid4().hex,
+            "events": [{
+                "finish_time": "2017-02-27T07:47:25.000000",
+                "start_time": "2017-02-27T07:47:15.000000",
+                "traceback": None,
+                "event": "compute__do_build_and_run_instance",
+                "result": "Success"
+            }]
+        }
+        # Overwrite default attributes
+        server_event_info.update(attrs)
+
+        server_event = fakes.FakeResource(
+            info=copy.deepcopy(server_event_info),
+            loaded=True,
+        )
+        return server_event
+
+
 class FakeService(object):
     """Fake one or more services."""
 
diff --git a/openstackclient/tests/unit/compute/v2/test_server_event.py b/openstackclient/tests/unit/compute/v2/test_server_event.py
new file mode 100644
index 0000000000..5c94891a14
--- /dev/null
+++ b/openstackclient/tests/unit/compute/v2/test_server_event.py
@@ -0,0 +1,167 @@
+#   Copyright 2017 Huawei, Inc. All rights reserved.
+#
+#   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.
+#
+
+from openstackclient.compute.v2 import server_event
+from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+
+
+class TestServerEvent(compute_fakes.TestComputev2):
+
+    fake_server = compute_fakes.FakeServer.create_one_server()
+
+    def setUp(self):
+        super(TestServerEvent, self).setUp()
+
+        self.servers_mock = self.app.client_manager.compute.servers
+        self.servers_mock.reset_mock()
+        self.events_mock = self.app.client_manager.compute.instance_action
+        self.events_mock.reset_mock()
+
+        self.servers_mock.get.return_value = self.fake_server
+
+
+class TestListServerEvent(TestServerEvent):
+
+    fake_event = compute_fakes.FakeServerEvent.create_one_server_event()
+
+    columns = (
+        'Request ID',
+        'Server ID',
+        'Action',
+        'Start Time',
+    )
+    data = ((
+        fake_event.request_id,
+        fake_event.instance_uuid,
+        fake_event.action,
+        fake_event.start_time,
+    ), )
+
+    long_columns = (
+        'Request ID',
+        'Server ID',
+        'Action',
+        'Start Time',
+        'Message',
+        'Project ID',
+        'User ID',
+    )
+    long_data = ((
+        fake_event.request_id,
+        fake_event.instance_uuid,
+        fake_event.action,
+        fake_event.start_time,
+        fake_event.message,
+        fake_event.project_id,
+        fake_event.user_id,
+    ), )
+
+    def setUp(self):
+        super(TestListServerEvent, self).setUp()
+
+        self.events_mock.list.return_value = [self.fake_event, ]
+        self.cmd = server_event.ListServerEvent(self.app, None)
+
+    def test_server_event_list(self):
+        arglist = [
+            self.fake_server.name,
+        ]
+        verifylist = [
+            ('server', self.fake_server.name),
+            ('long', False),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.servers_mock.get.assert_called_once_with(self.fake_server.name)
+        self.events_mock.list.assert_called_once_with(self.fake_server.id)
+
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, tuple(data))
+
+    def test_server_event_list_long(self):
+        arglist = [
+            '--long',
+            self.fake_server.name,
+        ]
+        verifylist = [
+            ('server', self.fake_server.name),
+            ('long', True),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.servers_mock.get.assert_called_once_with(self.fake_server.name)
+        self.events_mock.list.assert_called_once_with(self.fake_server.id)
+
+        self.assertEqual(self.long_columns, columns)
+        self.assertEqual(self.long_data, tuple(data))
+
+
+class TestShowServerEvent(TestServerEvent):
+
+    fake_event = compute_fakes.FakeServerEvent.create_one_server_event()
+
+    columns = (
+        'action',
+        'events',
+        'instance_uuid',
+        'message',
+        'project_id',
+        'request_id',
+        'start_time',
+        'user_id',
+    )
+    data = (
+        fake_event.action,
+        fake_event.events,
+        fake_event.instance_uuid,
+        fake_event.message,
+        fake_event.project_id,
+        fake_event.request_id,
+        fake_event.start_time,
+        fake_event.user_id,
+    )
+
+    def setUp(self):
+        super(TestShowServerEvent, self).setUp()
+
+        self.events_mock.get.return_value = self.fake_event
+        self.cmd = server_event.ShowServerEvent(self.app, None)
+
+    def test_server_event_show(self):
+        arglist = [
+            self.fake_server.name,
+            self.fake_event.request_id,
+        ]
+        verifylist = [
+            ('server', self.fake_server.name),
+            ('request_id', self.fake_event.request_id),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.servers_mock.get.assert_called_once_with(self.fake_server.name)
+        self.events_mock.get.assert_called_once_with(
+            self.fake_server.id, self.fake_event.request_id)
+
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
diff --git a/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml b/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml
new file mode 100644
index 0000000000..200c73fbe8
--- /dev/null
+++ b/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    Add ``server event`` list and show commands, that is similar to nova's
+    instance action commands.
+
+    Server event is the event record that had been done on a server,
+    include: event type(create, delete, reboot and so on),
+    event result(success, error), start time, finish time and so on.
+    These are important information for server maintains.
+    [Bug `1642030 <https://bugs.launchpad.net/python-openstackclient/+bug/1642030>`_]
diff --git a/setup.cfg b/setup.cfg
index e18aa5c1e7..ea4691b32e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -135,6 +135,9 @@ openstack.compute.v2 =
 
     server_backup_create = openstackclient.compute.v2.server_backup:CreateServerBackup
 
+    server_event_list = openstackclient.compute.v2.server_event:ListServerEvent
+    server_event_show = openstackclient.compute.v2.server_event:ShowServerEvent
+
     server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup
     server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup
     server_group_list = openstackclient.compute.v2.server_group:ListServerGroup