1# Copyright 2011 Denali Systems, Inc.
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16"""
17Volume interface
18"""
19import warnings
20
21from novaclient import api_versions
22from novaclient import base
23
24
25class Volume(base.Resource):
26    """
27    A volume is an extra block level storage to the OpenStack
28    instances.
29    """
30    NAME_ATTR = 'display_name'
31
32    def __repr__(self):
33        return "<Volume: %s>" % self.id
34
35
36class VolumeManager(base.Manager):
37    """
38    Manage :class:`Volume` resources. This is really about volume attachments.
39    """
40    resource_class = Volume
41
42    @api_versions.wraps("2.0", "2.48")
43    def create_server_volume(self, server_id, volume_id, device=None):
44        """
45        Attach a volume identified by the volume ID to the given server ID
46
47        :param server_id: The ID of the server
48        :param volume_id: The ID of the volume to attach.
49        :param device: The device name (optional)
50        :rtype: :class:`Volume`
51        """
52        body = {'volumeAttachment': {'volumeId': volume_id}}
53        if device is not None:
54            body['volumeAttachment']['device'] = device
55        return self._create("/servers/%s/os-volume_attachments" % server_id,
56                            body, "volumeAttachment")
57
58    @api_versions.wraps("2.49", "2.78")
59    def create_server_volume(self, server_id, volume_id, device=None,
60                             tag=None):
61        """
62        Attach a volume identified by the volume ID to the given server ID
63
64        :param server_id: The ID of the server
65        :param volume_id: The ID of the volume to attach.
66        :param device: The device name (optional)
67        :param tag: The tag (optional)
68        :rtype: :class:`Volume`
69        """
70        body = {'volumeAttachment': {'volumeId': volume_id}}
71        if device is not None:
72            body['volumeAttachment']['device'] = device
73        if tag is not None:
74            body['volumeAttachment']['tag'] = tag
75        return self._create("/servers/%s/os-volume_attachments" % server_id,
76                            body, "volumeAttachment")
77
78    @api_versions.wraps("2.79")
79    def create_server_volume(self, server_id, volume_id, device=None,
80                             tag=None, delete_on_termination=False):
81        """
82        Attach a volume identified by the volume ID to the given server ID
83
84        :param server_id: The ID of the server.
85        :param volume_id: The ID of the volume to attach.
86        :param device: The device name (optional).
87        :param tag: The tag (optional).
88        :param delete_on_termination: Marked whether to delete the attached
89                                      volume when the server is deleted
90                                      (optional).
91        :rtype: :class:`Volume`
92        """
93        # TODO(mriedem): Move this body construction into a private common
94        # helper method for all versions of create_server_volume to use.
95        body = {'volumeAttachment': {'volumeId': volume_id}}
96        if device is not None:
97            body['volumeAttachment']['device'] = device
98        if tag is not None:
99            body['volumeAttachment']['tag'] = tag
100        if delete_on_termination:
101            body['volumeAttachment']['delete_on_termination'] = (
102                delete_on_termination)
103        return self._create("/servers/%s/os-volume_attachments" % server_id,
104                            body, "volumeAttachment")
105
106    @api_versions.wraps("2.0", "2.84")
107    def update_server_volume(self, server_id, src_volid, dest_volid):
108        """
109        Swaps the existing volume attachment to point to a new volume.
110
111        Takes a server, a source (attached) volume and a destination volume and
112        performs a hypervisor assisted data migration from src to dest volume,
113        detaches the original (source) volume and attaches the new destination
114        volume. Note that not all backing hypervisor drivers support this
115        operation and it may be disabled via policy.
116
117
118        :param server_id: The ID of the server
119        :param source_volume: The ID of the src volume
120        :param dest_volume: The ID of the destination volume
121        :rtype: :class:`Volume`
122        """
123        body = {'volumeAttachment': {'volumeId': dest_volid}}
124        return self._update("/servers/%s/os-volume_attachments/%s" %
125                            (server_id, src_volid,),
126                            body, "volumeAttachment")
127
128    @api_versions.wraps("2.85")
129    def update_server_volume(self, server_id, src_volid, dest_volid,
130                             delete_on_termination=None):
131        """
132        Swaps the existing volume attachment to point to a new volume.
133
134        Takes a server, a source (attached) volume and a destination volume and
135        performs a hypervisor assisted data migration from src to dest volume,
136        detaches the original (source) volume and attaches the new destination
137        volume. Note that not all backing hypervisor drivers support this
138        operation and it may be disabled via policy.
139
140
141        :param server_id: The ID of the server
142        :param source_volume: The ID of the src volume
143        :param dest_volume: The ID of the destination volume
144        :param delete_on_termination: Marked whether to delete the attached
145                                      volume when the server is deleted
146                                      (optional).
147        :rtype: :class:`Volume`
148        """
149        body = {'volumeAttachment': {'volumeId': dest_volid}}
150        if delete_on_termination is not None:
151            body['volumeAttachment']['delete_on_termination'] = (
152                delete_on_termination)
153        return self._update("/servers/%s/os-volume_attachments/%s" %
154                            (server_id, src_volid),
155                            body, "volumeAttachment")
156
157    def get_server_volume(self, server_id, volume_id=None, attachment_id=None):
158        """
159        Get the volume identified by the volume ID, that is attached to
160        the given server ID
161
162        :param server_id: The ID of the server
163        :param volume_id: The ID of the volume to attach
164        :rtype: :class:`Volume`
165        """
166
167        if attachment_id is not None and volume_id is not None:
168            raise TypeError("You cannot specify both volume_id "
169                            "and attachment_id arguments.")
170
171        elif attachment_id is not None:
172            warnings.warn("attachment_id argument "
173                          "of volumes.get_server_volume "
174                          "method is deprecated in favor "
175                          "of volume_id.")
176            volume_id = attachment_id
177
178        if volume_id is None:
179            raise TypeError("volume_id is required argument.")
180
181        return self._get("/servers/%s/os-volume_attachments/%s" % (server_id,
182                         volume_id,), "volumeAttachment")
183
184    def get_server_volumes(self, server_id):
185        """
186        Get a list of all the attached volumes for the given server ID
187
188        :param server_id: The ID of the server
189        :rtype: list of :class:`Volume`
190        """
191        return self._list("/servers/%s/os-volume_attachments" % server_id,
192                          "volumeAttachments")
193
194    def delete_server_volume(self, server_id, volume_id=None,
195                             attachment_id=None):
196        """
197        Detach a volume identified by the volume ID from the given server
198
199        :param server_id: The ID of the server
200        :param volume_id: The ID of the volume to attach
201        :returns: An instance of novaclient.base.TupleWithMeta
202        """
203        if attachment_id is not None and volume_id is not None:
204            raise TypeError("You cannot specify both volume_id "
205                            "and attachment_id arguments.")
206
207        elif attachment_id is not None:
208            warnings.warn("attachment_id argument "
209                          "of volumes.delete_server_volume "
210                          "method is deprecated in favor "
211                          "of volume_id.")
212            volume_id = attachment_id
213
214        if volume_id is None:
215            raise TypeError("volume_id is required argument.")
216
217        return self._delete("/servers/%s/os-volume_attachments/%s" %
218                            (server_id, volume_id,))
219