diff --git a/novaclient/tests/v3/fakes.py b/novaclient/tests/v3/fakes.py index 4291b13cb..772141411 100644 --- a/novaclient/tests/v3/fakes.py +++ b/novaclient/tests/v3/fakes.py @@ -188,7 +188,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient): 'reset_state': ['state'], 'create_image': ['name', 'metadata'], '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 action = list(body)[0] diff --git a/novaclient/tests/v3/test_volumes.py b/novaclient/tests/v3/test_volumes.py new file mode 100644 index 000000000..82f6d54a0 --- /dev/null +++ b/novaclient/tests/v3/test_volumes.py @@ -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') diff --git a/novaclient/v3/client.py b/novaclient/v3/client.py index 0d8e8c3f7..07641d181 100644 --- a/novaclient/v3/client.py +++ b/novaclient/v3/client.py @@ -30,6 +30,7 @@ from novaclient.v3 import quotas from novaclient.v3 import servers from novaclient.v3 import services from novaclient.v3 import usage +from novaclient.v3 import volumes class Client(object): @@ -80,6 +81,7 @@ class Client(object): self.servers = servers.ServerManager(self) self.services = services.ServiceManager(self) self.usage = usage.UsageManager(self) + self.volumes = volumes.VolumeManager(self) # Add in any extensions... if extensions: diff --git a/novaclient/v3/shell.py b/novaclient/v3/shell.py index b183860b4..2516086ed 100644 --- a/novaclient/v3/shell.py +++ b/novaclient/v3/shell.py @@ -1547,10 +1547,25 @@ def do_volume_attach(cs, args): if args.device == 'auto': 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.device) - _print_volume(volume) + + +@utils.arg('server', + metavar='', + help='Name or ID of server.') +@utils.arg('attachment_id', + metavar='', + help='Attachment ID of the volume.') +@utils.arg('new_volume', + metavar='', + 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', diff --git a/novaclient/v3/volumes.py b/novaclient/v3/volumes.py new file mode 100644 index 000000000..ec061a319 --- /dev/null +++ b/novaclient/v3/volumes.py @@ -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)