summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/oneview/deploy_utils.py
blob: 4a032aefe72b65a44670128ba6b2cc9cf739ed51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# Copyright 2016 Hewlett Packard Enterprise Development LP.
# Copyright 2016 Universidade Federal de Campina Grande
# 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 operator

from oslo_log import log as logging
from oslo_utils import importutils

from ironic.common import exception
from ironic.common.i18n import _, _LE, _LI, _LW
from ironic.common import states
from ironic.drivers.modules.oneview import common

LOG = logging.getLogger(__name__)

oneview_exception = importutils.try_import('oneview_client.exceptions')
oneview_utils = importutils.try_import('oneview_client.utils')


def get_properties():
    return common.COMMON_PROPERTIES


def prepare(oneview_client, task):
    """Applies Server Profile and update the node when preparing.

    This method is responsible for applying a Server Profile to the Server
    Hardware and add the uri of the applied Server Profile in the node's
    'applied_server_profile_uri' field on properties/capabilities.

    :param oneview_client: an instance of the OneView client
    :param task: A TaskManager object
    :raises InstanceDeployFailure: If the node doesn't have the needed OneView
            informations, if Server Hardware is in use by an OneView user, or
            if the Server Profile can't be applied.

    """
    if task.node.provision_state == states.DEPLOYING:
        try:
            instance_display_name = task.node.instance_info.get('display_name')
            instance_uuid = task.node.instance_uuid
            server_profile_name = (
                "%(instance_name)s [%(instance_uuid)s]" %
                {"instance_name": instance_display_name,
                 "instance_uuid": instance_uuid}
            )
            allocate_server_hardware_to_ironic(oneview_client, task.node,
                                               server_profile_name)
        except exception.OneViewError as e:
            raise exception.InstanceDeployFailure(node=task.node.uuid,
                                                  reason=e)


def tear_down(oneview_client, task):
    """Remove Server profile and update the node when tear down.

    This method is responsible for power a Server Hardware off, remove a Server
    Profile from the Server Hardware and remove the uri of the applied Server
    Profile from the node's 'applied_server_profile_uri' in
    properties/capabilities.

    :param oneview_client: an instance of the OneView client
    :param task: A TaskManager object
    :raises InstanceDeployFailure: If node has no uri of applied Server
            Profile, or if some error occur while deleting Server Profile.

    """
    try:
        deallocate_server_hardware_from_ironic(oneview_client, task.node)
    except exception.OneViewError as e:
        raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e)


def prepare_cleaning(oneview_client, task):
    """Applies Server Profile and update the node when preparing cleaning.

    This method is responsible for applying a Server Profile to the Server
    Hardware and add the uri of the applied Server Profile in the node's
    'applied_server_profile_uri' field on properties/capabilities.

    :param oneview_client: an instance of the OneView client
    :param task: A TaskManager object
    :raises NodeCleaningFailure: If the node doesn't have the needed OneView
            informations, if Server Hardware is in use by an OneView user, or
            if the Server Profile can't be applied.

    """
    try:
        server_profile_name = "Ironic Cleaning [%s]" % task.node.uuid
        allocate_server_hardware_to_ironic(oneview_client, task.node,
                                           server_profile_name)
    except exception.OneViewError as e:
        oneview_error = common.SERVER_HARDWARE_ALLOCATION_ERROR
        driver_internal_info = task.node.driver_internal_info
        driver_internal_info['oneview_error'] = oneview_error
        task.node.driver_internal_info = driver_internal_info
        task.node.save()
        raise exception.NodeCleaningFailure(node=task.node.uuid,
                                            reason=e)


def tear_down_cleaning(oneview_client, task):
    """Remove Server profile and update the node when tear down cleaning.

    This method is responsible for power a Server Hardware off, remove a Server
    Profile from the Server Hardware and remove the uri of the applied Server
    Profile from the node's 'applied_server_profile_uri' in
    properties/capabilities.

    :param oneview_client: an instance of the OneView client
    :param task: A TaskManager object
    :raises NodeCleaningFailure: If node has no uri of applied Server Profile,
            or if some error occur while deleting Server Profile.

    """
    try:
        deallocate_server_hardware_from_ironic(oneview_client, task.node)
    except exception.OneViewError as e:
        raise exception.NodeCleaningFailure(node=task.node.uuid, reason=e)


def _is_node_in_use(server_hardware, applied_sp_uri, by_oneview=False):
    """Check if node is in use by ironic or by OneView.

    :param by_oneview: Boolean value. True when want to verify if node is in
                       use by OneView. False to verify if node is in use by
                       ironic.
    :param node: an ironic node object
    :returns: Boolean value. True if by_oneview param is also True and node is
              in use by OneView, False otherwise. True if by_oneview param is
              False and node is in use by ironic, False otherwise.

    """

    operation = operator.ne if by_oneview else operator.eq
    return (server_hardware.server_profile_uri not in (None, '') and
            operation(applied_sp_uri, server_hardware.server_profile_uri))


def is_node_in_use_by_oneview(oneview_client, node):
    """Check if node is in use by OneView user.

    :param oneview_client: an instance of the OneView client
    :param node: an ironic node object
    :returns: Boolean value. True if node is in use by OneView,
              False otherwise.
    :raises OneViewError: if not possible to get OneView's informations
            for the given node, if not possible to retrieve Server Hardware
            from OneView.

    """

    positive = _("Node '%s' is in use by OneView.") % node.uuid
    negative = _("Node '%s' is not in use by OneView.") % node.uuid

    def predicate(server_hardware, applied_sp_uri):
        # Check if Profile exists in Oneview and it is different of the one
        # applied by ironic
        return _is_node_in_use(server_hardware, applied_sp_uri,
                               by_oneview=True)

    return _check_applied_server_profile(oneview_client, node,
                                         predicate, positive, negative)


def is_node_in_use_by_ironic(oneview_client, node):
    """Check if node is in use by ironic in OneView.

    :param oneview_client: an instance of the OneView client
    :param node: an ironic node object
    :returns: Boolean value. True if node is in use by ironic,
              False otherwise.
    :raises OneViewError: if not possible to get OneView's information
            for the given node, if not possible to retrieve Server Hardware
            from OneView.

    """

    positive = _("Node '%s' is in use by Ironic.") % node.uuid
    negative = _("Node '%s' is not in use by Ironic.") % node.uuid

    def predicate(server_hardware, applied_sp_uri):
        # Check if Profile exists in Oneview and it is equals of the one
        # applied by ironic
        return _is_node_in_use(server_hardware, applied_sp_uri,
                               by_oneview=False)

    return _check_applied_server_profile(oneview_client, node,
                                         predicate, positive, negative)


def _check_applied_server_profile(oneview_client, node,
                                  predicate, positive, negative):
    """Check if node is in use by ironic in OneView.

    :param oneview_client: an instance of the OneView client
    :param node: an ironic node object
    :returns: Boolean value. True if node is in use by ironic,
              False otherwise.
    :raises OneViewError: if not possible to get OneView's information
             for the given node, if not possible to retrieve Server Hardware
             from OneView.

    """
    oneview_info = common.get_oneview_info(node)

    sh_uuid = oneview_utils.get_uuid_from_uri(
        oneview_info.get("server_hardware_uri")
    )

    try:
        server_hardware = oneview_client.get_server_hardware_by_uuid(
            sh_uuid
        )
    except oneview_exception.OneViewResourceNotFoundError as e:
        msg = (_("Error while obtaining Server Hardware from node "
                 "%(node_uuid)s. Error: %(error)s") %
               {'node_uuid': node.uuid, 'error': e})
        raise exception.OneViewError(error=msg)

    applied_sp_uri = (
        node.driver_info.get('applied_server_profile_uri')
    )

    result = predicate(server_hardware, applied_sp_uri)

    if result:
        LOG.debug(positive)
    else:
        LOG.debug(negative)

    return result


def _add_applied_server_profile_uri_field(node, applied_profile):
    """Adds the applied Server Profile uri to a node.

    :param node: an ironic node object

    """
    driver_info = node.driver_info
    driver_info['applied_server_profile_uri'] = applied_profile.uri
    node.driver_info = driver_info
    node.save()


def _del_applied_server_profile_uri_field(node):
    """Delete the applied Server Profile uri from a node if it exists.

    :param node: an ironic node object

    """
    driver_info = node.driver_info
    driver_info.pop('applied_server_profile_uri', None)
    node.driver_info = driver_info
    node.save()


def allocate_server_hardware_to_ironic(oneview_client, node,
                                       server_profile_name):
    """Allocate Server Hardware to ironic.

    :param oneview_client: an instance of the OneView client
    :param node: an ironic node object
    :param server_profile_name: a formatted string with the Server Profile
           name
    :raises OneViewError: if an error occurs while allocating the Server
            Hardware to ironic

    """
    node_in_use_by_oneview = is_node_in_use_by_oneview(oneview_client, node)

    if not node_in_use_by_oneview:

        oneview_info = common.get_oneview_info(node)

        applied_sp_uri = node.driver_info.get('applied_server_profile_uri')

        sh_uuid = oneview_utils.get_uuid_from_uri(
            oneview_info.get("server_hardware_uri")
        )
        spt_uuid = oneview_utils.get_uuid_from_uri(
            oneview_info.get("server_profile_template_uri")
        )
        server_hardware = oneview_client.get_server_hardware_by_uuid(sh_uuid)

        # Don't have Server Profile on OneView but has
        # `applied_server_profile_uri` on driver_info
        if (server_hardware.server_profile_uri in (None, '') and
                applied_sp_uri is not (None, '')):

            _del_applied_server_profile_uri_field(node)
            LOG.info(_LI(
                "Inconsistent 'applied_server_profile_uri' parameter "
                "value in driver_info. There is no Server Profile "
                "applied to node %(node_uuid)s. Value deleted."),
                {"node_uuid": node.uuid}
            )

        # applied_server_profile_uri exists and is equal to Server profile
        # applied on Hardware. Do not apply again.
        if (applied_sp_uri and server_hardware.server_profile_uri and
            server_hardware.server_profile_uri == applied_sp_uri):
            LOG.info(_LI(
                "The Server Profile %(applied_sp_uri)s was already applied "
                "by ironic on node %(node_uuid)s. Reusing."),
                {"node_uuid": node.uuid, "applied_sp_uri": applied_sp_uri}
            )
            return

        try:
            applied_profile = oneview_client.clone_template_and_apply(
                server_profile_name, sh_uuid, spt_uuid
            )
            _add_applied_server_profile_uri_field(node, applied_profile)

            LOG.info(
                _LI("Server Profile %(server_profile_uuid)s was successfully"
                    " applied to node %(node_uuid)s."),
                {"node_uuid": node.uuid,
                 "server_profile_uuid": applied_profile.uri}
            )

        except oneview_exception.OneViewServerProfileAssignmentError as e:
            LOG.error(_LE("An error occurred during allocating server "
                          "hardware to ironic during prepare: %s"), e)
            raise exception.OneViewError(error=e)
    else:
        msg = (_("Node %s is already in use by OneView.") %
               node.uuid)

        raise exception.OneViewError(error=msg)


def deallocate_server_hardware_from_ironic(oneview_client, node):
    """Deallocate Server Hardware from ironic.

    :param oneview_client: an instance of the OneView client
    :param node: an ironic node object
    :raises OneViewError: if an error occurs while deallocating the Server
            Hardware to ironic

    """

    if is_node_in_use_by_ironic(oneview_client, node):

        oneview_info = common.get_oneview_info(node)
        server_profile_uuid = oneview_utils.get_uuid_from_uri(
            oneview_info.get('applied_server_profile_uri')
        )

        try:
            oneview_client.power_off(oneview_info)
            oneview_client.delete_server_profile(server_profile_uuid)
            _del_applied_server_profile_uri_field(node)

            LOG.info(_LI("Server Profile %(server_profile_uuid)s was deleted "
                         "from node %(node_uuid)s in OneView."),
                     {'server_profile_uuid': server_profile_uuid,
                      'node_uuid': node.uuid})
        except (ValueError, oneview_exception.OneViewException) as e:
            msg = (_("Error while deleting applied Server Profile from node "
                     "%(node_uuid)s. Error: %(error)s") %
                   {'node_uuid': node.uuid, 'error': e})
            raise exception.OneViewError(error=msg)

    else:
        LOG.warning(_LW("Cannot deallocate node %(node_uuid)s "
                        "in OneView because it is not in use by "
                        "ironic."), {'node_uuid': node.uuid})