Adds volume support for the V3 API

Adds the ability to attach, detach and swap volumes on
servers. There is no code shared with the v1_1 version
because for V3 the volumes interface is completely different
and the attach/detach/swap functionality is simply a server
action rather than something accessed through a special resource.

Partially implements blueprint v3-api

Change-Id: Ib405f821fe557745d11cff9db08381fc15233fe5
This commit is contained in:
Chris Yeoh 2013-12-24 00:28:13 +10:30 committed by Christopher Yeoh
parent db6c58b009
commit 61d88463da
5 changed files with 138 additions and 3 deletions

View File

@ -188,7 +188,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
'reset_state': ['state'], 'reset_state': ['state'],
'create_image': ['name', 'metadata'], 'create_image': ['name', 'metadata'],
'migrate_live': ['host', 'block_migration', 'disk_over_commit'], 'migrate_live': ['host', 'block_migration', 'disk_over_commit'],
'create_backup': ['name', 'backup_type', 'rotation']} 'create_backup': ['name', 'backup_type', 'rotation'],
'attach': ['volume_id', 'device'],
'detach': ['volume_id'],
'swap_volume_attachment': ['old_volume_id', 'new_volume_id']}
assert len(body.keys()) == 1 assert len(body.keys()) == 1
action = list(body)[0] action = list(body)[0]

View File

@ -0,0 +1,47 @@
# Copyright 2013 IBM Corp.
# 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 novaclient.tests import utils
from novaclient.tests.v3 import fakes
class VolumesTest(utils.TestCase):
def setUp(self):
super(VolumesTest, self).setUp()
self.cs = self._get_fake_client()
def _get_fake_client(self):
return fakes.FakeClient()
def test_attach_server_volume(self):
v = self.cs.volumes.attach_server_volume(
server=1234,
volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983',
device='/dev/vdb'
)
self.cs.assert_called('POST', '/servers/1234/action')
def test_update_server_volume(self):
vol_id = '15e59938-07d5-11e1-90e3-e3dffe0c5983'
v = self.cs.volumes.update_server_volume(
server=1234,
old_volume_id='Work',
new_volume_id=vol_id
)
self.cs.assert_called('POST', '/servers/1234/action')
def test_delete_server_volume(self):
self.cs.volumes.delete_server_volume(1234, 'Work')
self.cs.assert_called('POST', '/servers/1234/action')

View File

@ -30,6 +30,7 @@ from novaclient.v3 import quotas
from novaclient.v3 import servers from novaclient.v3 import servers
from novaclient.v3 import services from novaclient.v3 import services
from novaclient.v3 import usage from novaclient.v3 import usage
from novaclient.v3 import volumes
class Client(object): class Client(object):
@ -80,6 +81,7 @@ class Client(object):
self.servers = servers.ServerManager(self) self.servers = servers.ServerManager(self)
self.services = services.ServiceManager(self) self.services = services.ServiceManager(self)
self.usage = usage.UsageManager(self) self.usage = usage.UsageManager(self)
self.volumes = volumes.VolumeManager(self)
# Add in any extensions... # Add in any extensions...
if extensions: if extensions:

View File

@ -1547,10 +1547,25 @@ def do_volume_attach(cs, args):
if args.device == 'auto': if args.device == 'auto':
args.device = None args.device = None
volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id, volume = cs.volumes.attach_server_volume(_find_server(cs, args.server).id,
args.volume, args.volume,
args.device) args.device)
_print_volume(volume)
@utils.arg('server',
metavar='<server>',
help='Name or ID of server.')
@utils.arg('attachment_id',
metavar='<volume>',
help='Attachment ID of the volume.')
@utils.arg('new_volume',
metavar='<volume>',
help='ID of the volume to attach.')
def do_volume_update(cs, args):
"""Update volume attachment."""
volume = cs.volumes.update_server_volume(_find_server(cs, args.server).id,
args.attachment_id,
args.new_volume)
@utils.arg('server', @utils.arg('server',

68
novaclient/v3/volumes.py Normal file
View File

@ -0,0 +1,68 @@
# Copyright 2013 IBM Corp.
#
# 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.
"""
Volume interface
"""
from novaclient import base
class VolumeManager(base.Manager):
"""
Manage :class:`Volume` resources.
"""
def attach_server_volume(self, server, volume_id, device):
"""
Attach a volume identified by the volume ID to the given server ID
:param server: The server (or it's ID)
:param volume_id: The ID of the volume to attach.
:param device: The device name
:rtype: :class:`Volume`
"""
body = {'volume_id': volume_id, 'device': device}
return self._action('attach', server, body)
def update_server_volume(self, server, old_volume_id, new_volume_id):
"""
Update the volume identified by the attachment ID, that is attached to
the given server ID
:param server_id: The server (or it's ID)
:param old_volume_id: The ID of the attachment
:param new_volume_id: The ID of the new volume to attach
:rtype: :class:`Volume`
"""
body = {'new_volume_id': new_volume_id, 'old_volume_id': old_volume_id}
return self._action('swap_volume_attachment', server, body)
def delete_server_volume(self, server, volume_id):
"""
Detach a volume identified by the attachment ID from the given server
:param server_id: The ID of the server
:param volume_id: The ID of the attachment
"""
return self._action('detach', server, {'volume_id': volume_id})
def _action(self, action, server, info=None, **kwargs):
"""
Perform a server "action" -- reboot/rebuild/resize/etc.
"""
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/servers/%s/action' % base.getid(server)
return self.api.client.post(url, body=body)