Merge "Move server image create command to its own resource file."

This commit is contained in:
Jenkins 2016-06-16 13:05:18 +00:00 committed by Gerrit Code Review
commit b9a318156c
7 changed files with 346 additions and 224 deletions

View File

@ -10,7 +10,7 @@ Compute v2
server image create server image create
------------------- -------------------
Create a new disk image from a running server Create a new server disk image from an existing server
.. program:: server image create .. program:: server image create
.. code:: bash .. code:: bash
@ -22,12 +22,12 @@ Create a new disk image from a running server
.. option:: --name <image-name> .. option:: --name <image-name>
Name of new image (default is server name) Name of new disk image (default: server name)
.. option:: --wait .. option:: --wait
Wait for image create to complete Wait for operation to complete
.. describe:: <server> .. describe:: <server>
Server (name or ID) Server to create image (name or ID)

View File

@ -19,11 +19,12 @@ class HelpTests(test.TestCase):
SERVER_COMMANDS = [ SERVER_COMMANDS = [
('server add security group', 'Add security group to server'), ('server add security group', 'Add security group to server'),
('server add volume', 'Add volume to server'), ('server add volume', 'Add volume to server'),
('server backup create', 'Create a server backup image'),
('server create', 'Create a new server'), ('server create', 'Create a new server'),
('server delete', 'Delete server(s)'), ('server delete', 'Delete server(s)'),
('server dump create', 'Create a dump file in server(s)'), ('server dump create', 'Create a dump file in server(s)'),
('server image create', ('server image create',
'Create a new disk image from a running server'), 'Create a new server disk image from an existing server'),
('server list', 'List servers'), ('server list', 'List servers'),
('server lock', ('server lock',
'Lock server(s). ' 'Lock server(s). '

View File

@ -164,23 +164,6 @@ def _prep_server_detail(compute_client, server):
return info return info
def _prep_image_detail(image_client, image_id):
"""Prepare the detailed image dict for printing
:param image_client: an image client instance
:param image_id: id of image created
:rtype: a dict of image details
"""
info = utils.find_resource(
image_client.images,
image_id,
)
# Glance client V2 doesn't have _info attribute
# The following condition deals with it.
return getattr(info, "_info", info)
def _show_progress(progress): def _show_progress(progress):
if progress: if progress:
sys.stdout.write('\rProgress: %s' % progress) sys.stdout.write('\rProgress: %s' % progress)
@ -597,63 +580,6 @@ class CreateServerDump(command.Command):
).trigger_crash_dump() ).trigger_crash_dump()
class CreateServerImage(command.ShowOne):
"""Create a new disk image from a running server"""
def get_parser(self, prog_name):
parser = super(CreateServerImage, self).get_parser(prog_name)
parser.add_argument(
'server',
metavar='<server>',
help=_('Server (name or ID)'),
)
parser.add_argument(
'--name',
metavar='<image-name>',
help=_('Name of new image (default is server name)'),
)
parser.add_argument(
'--wait',
action='store_true',
help=_('Wait for image create to complete'),
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
image_client = self.app.client_manager.image
server = utils.find_resource(
compute_client.servers,
parsed_args.server,
)
if parsed_args.name:
name = parsed_args.name
else:
name = server.name
image_id = compute_client.servers.create_image(
server,
name,
)
if parsed_args.wait:
if utils.wait_for_status(
image_client.images.get,
image_id,
callback=_show_progress,
):
sys.stdout.write('\n')
else:
self.log.error(_('Error creating snapshot of server: %s'),
parsed_args.server)
sys.stdout.write(_('Error creating server snapshot\n'))
raise SystemExit
image = _prep_image_detail(image_client, image_id)
return zip(*sorted(six.iteritems(image)))
class DeleteServer(command.Command): class DeleteServer(command.Command):
"""Delete server(s)""" """Delete server(s)"""

View File

@ -0,0 +1,111 @@
# Copyright 2012-2013 OpenStack Foundation
#
# 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 action implementations"""
import sys
from oslo_utils import importutils
import six
from openstackclient.common import command
from openstackclient.common import exceptions
from openstackclient.common import utils
from openstackclient.i18n import _
def _show_progress(progress):
if progress:
sys.stdout.write('\rProgress: %s' % progress)
sys.stdout.flush()
class CreateServerImage(command.ShowOne):
"""Create a new server disk image from an existing server"""
IMAGE_API_VERSIONS = {
"1": "openstackclient.image.v1.image",
"2": "openstackclient.image.v2.image",
}
def get_parser(self, prog_name):
parser = super(CreateServerImage, self).get_parser(prog_name)
parser.add_argument(
'server',
metavar='<server>',
help=_('Server to create image (name or ID)'),
)
parser.add_argument(
'--name',
metavar='<image-name>',
help=_('Name of new disk image (default: server name)'),
)
parser.add_argument(
'--wait',
action='store_true',
help=_('Wait for operation to complete'),
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers,
parsed_args.server,
)
if parsed_args.name:
image_name = parsed_args.name
else:
image_name = server.name
image_id = compute_client.servers.create_image(
server.id,
image_name,
)
image_client = self.app.client_manager.image
image = utils.find_resource(
image_client.images,
image_id,
)
if parsed_args.wait:
if utils.wait_for_status(
image_client.images.get,
image_id,
callback=_show_progress,
):
sys.stdout.write('\n')
else:
self.log.error(
_('Error creating server image: %s') %
parsed_args.server,
)
raise exceptions.CommandError
if self.app.client_manager._api_version['image'] == '1':
info = {}
info.update(image._info)
info['properties'] = utils.format_dict(info.get('properties', {}))
else:
# Get the right image module to format the output
image_module = importutils.import_module(
self.IMAGE_API_VERSIONS[
self.app.client_manager._api_version['image']
]
)
info = image_module._format_image(image)
return zip(*sorted(six.iteritems(info)))

View File

@ -511,150 +511,6 @@ class TestServerDumpCreate(TestServer):
self.run_method_with_servers('trigger_crash_dump', 3) self.run_method_with_servers('trigger_crash_dump', 3)
class TestServerImageCreate(TestServer):
columns = (
'id',
'name',
'owner',
'protected',
'tags',
'visibility',
)
def datalist(self):
datalist = (
self.image.id,
self.image.name,
self.image.owner,
self.image.protected,
self.image.tags,
self.image.visibility,
)
return datalist
def setUp(self):
super(TestServerImageCreate, self).setUp()
self.server = compute_fakes.FakeServer.create_one_server()
# This is the return value for utils.find_resource()
self.servers_mock.get.return_value = self.server
self.image = image_fakes.FakeImage.create_one_image()
self.images_mock.get.return_value = self.image
self.servers_mock.create_image.return_value = self.image.id
# Get the command object to test
self.cmd = server.CreateServerImage(self.app, None)
def test_server_image_create_no_options(self):
arglist = [
self.server.id,
]
verifylist = [
('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
self.servers_mock.get.return_value,
self.server.name,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
def test_server_image_create_name(self):
arglist = [
'--name', 'img-nam',
self.server.id,
]
verifylist = [
('name', 'img-nam'),
('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
self.servers_mock.get.return_value,
'img-nam',
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
def test_server_create_image_with_wait_fails(self, mock_wait_for_status):
arglist = [
'--wait',
self.server.id,
]
verifylist = [
('wait', True),
('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(SystemExit, self.cmd.take_action, parsed_args)
mock_wait_for_status.assert_called_once_with(
self.images_mock.get,
self.image.id,
callback=server._show_progress
)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
self.servers_mock.get.return_value,
self.server.name,
)
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
def test_server_create_image_with_wait_ok(self, mock_wait_for_status):
arglist = [
'--wait',
self.server.id,
]
verifylist = [
('wait', True),
('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
self.servers_mock.get.return_value,
self.server.name,
)
mock_wait_for_status.assert_called_once_with(
self.images_mock.get,
self.image.id,
callback=server._show_progress
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
class TestServerList(TestServer): class TestServerList(TestServer):
# Columns to be listed up. # Columns to be listed up.

View File

@ -0,0 +1,227 @@
# 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 mock
from openstackclient.common import exceptions
from openstackclient.common import utils as common_utils
from openstackclient.compute.v2 import server_image
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
class TestServerImage(compute_fakes.TestComputev2):
def setUp(self):
super(TestServerImage, self).setUp()
# Get a shortcut to the compute client ServerManager Mock
self.servers_mock = self.app.client_manager.compute.servers
self.servers_mock.reset_mock()
# Get a shortcut to the image client ImageManager Mock
self.images_mock = self.app.client_manager.image.images
self.images_mock.reset_mock()
# Set object attributes to be tested. Could be overwriten in subclass.
self.attrs = {}
# Set object methods to be tested. Could be overwriten in subclass.
self.methods = {}
def setup_servers_mock(self, count):
servers = compute_fakes.FakeServer.create_servers(
attrs=self.attrs,
methods=self.methods,
count=count,
)
# This is the return value for utils.find_resource()
self.servers_mock.get = compute_fakes.FakeServer.get_servers(
servers,
0,
)
return servers
class TestServerImageCreate(TestServerImage):
def image_columns(self, image):
columnlist = tuple(sorted(image.keys()))
return columnlist
def image_data(self, image):
datalist = (
image['id'],
image['name'],
image['owner'],
image['protected'],
'active',
common_utils.format_list(image.get('tags')),
image['visibility'],
)
return datalist
def setUp(self):
super(TestServerImageCreate, self).setUp()
# Get the command object to test
self.cmd = server_image.CreateServerImage(self.app, None)
self.methods = {
'create_image': None,
}
def setup_images_mock(self, count, servers=None):
if servers:
images = image_fakes.FakeImage.create_images(
attrs={
'name': servers[0].name,
'status': 'active',
},
count=count,
)
else:
images = image_fakes.FakeImage.create_images(
attrs={
'status': 'active',
},
count=count,
)
self.images_mock.get = mock.MagicMock(side_effect=images)
self.servers_mock.create_image = mock.MagicMock(
return_value=images[0].id,
)
return images
def test_server_image_create_defaults(self):
servers = self.setup_servers_mock(count=1)
images = self.setup_images_mock(count=1, servers=servers)
arglist = [
servers[0].id,
]
verifylist = [
('server', servers[0].id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
servers[0].id,
servers[0].name,
)
self.assertEqual(self.image_columns(images[0]), columns)
self.assertEqual(self.image_data(images[0]), data)
def test_server_image_create_options(self):
servers = self.setup_servers_mock(count=1)
images = self.setup_images_mock(count=1, servers=servers)
arglist = [
'--name', 'img-nam',
servers[0].id,
]
verifylist = [
('name', 'img-nam'),
('server', servers[0].id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
servers[0].id,
'img-nam',
)
self.assertEqual(self.image_columns(images[0]), columns)
self.assertEqual(self.image_data(images[0]), data)
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
def test_server_create_image_wait_fail(self, mock_wait_for_status):
servers = self.setup_servers_mock(count=1)
images = self.setup_images_mock(count=1, servers=servers)
arglist = [
'--wait',
servers[0].id,
]
verifylist = [
('wait', True),
('server', servers[0].id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args,
)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
servers[0].id,
servers[0].name,
)
mock_wait_for_status.assert_called_once_with(
self.images_mock.get,
images[0].id,
callback=mock.ANY
)
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
def test_server_create_image_wait_ok(self, mock_wait_for_status):
servers = self.setup_servers_mock(count=1)
images = self.setup_images_mock(count=1, servers=servers)
arglist = [
'--wait',
servers[0].id,
]
verifylist = [
('wait', True),
('server', servers[0].id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
servers[0].id,
servers[0].name,
)
mock_wait_for_status.assert_called_once_with(
self.images_mock.get,
images[0].id,
callback=mock.ANY
)
self.assertEqual(self.image_columns(images[0]), columns)
self.assertEqual(self.image_data(images[0]), data)

View File

@ -104,7 +104,6 @@ openstack.compute.v2 =
server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_add_volume = openstackclient.compute.v2.server:AddServerVolume
server_create = openstackclient.compute.v2.server:CreateServer server_create = openstackclient.compute.v2.server:CreateServer
server_delete = openstackclient.compute.v2.server:DeleteServer server_delete = openstackclient.compute.v2.server:DeleteServer
server_image_create = openstackclient.compute.v2.server:CreateServerImage
server_list = openstackclient.compute.v2.server:ListServer server_list = openstackclient.compute.v2.server:ListServer
server_lock = openstackclient.compute.v2.server:LockServer server_lock = openstackclient.compute.v2.server:LockServer
server_migrate = openstackclient.compute.v2.server:MigrateServer server_migrate = openstackclient.compute.v2.server:MigrateServer
@ -138,6 +137,8 @@ openstack.compute.v2 =
server_group_list = openstackclient.compute.v2.server_group:ListServerGroup server_group_list = openstackclient.compute.v2.server_group:ListServerGroup
server_group_show = openstackclient.compute.v2.server_group:ShowServerGroup server_group_show = openstackclient.compute.v2.server_group:ShowServerGroup
server_image_create = openstackclient.compute.v2.server_image:CreateServerImage
usage_list = openstackclient.compute.v2.usage:ListUsage usage_list = openstackclient.compute.v2.usage:ListUsage
usage_show = openstackclient.compute.v2.usage:ShowUsage usage_show = openstackclient.compute.v2.usage:ShowUsage