summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/oneview/deploy_utils.py
blob: ad6a711e0fae2985768cd078b6def3e14110319a (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-2017) Hewlett Packard Enterprise Development LP
# Copyright (2016-2017) Universidade Federal de Campina Grande
#
#    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 _
from ironic.common import states
from ironic.drivers.modules.oneview import common

LOG = logging.getLogger(__name__)

client_exception = importutils.try_import('hpOneView.exceptions')
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(client, task):
    """Apply 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 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(client, task.node,
                                               server_profile_name)
        except exception.OneViewError as e:
            raise exception.InstanceDeployFailure(node=task.node.uuid,
                                                  reason=e)


def tear_down(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 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(client, task)
    except exception.OneViewError as e:
        raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e)


def prepare_cleaning(client, task):
    """Apply 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 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(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(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 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(client, task)
    except exception.OneViewError as e:
        raise exception.NodeCleaningFailure(node=task.node.uuid, reason=e)


def _create_profile_from_template(
        client, server_profile_name,
        server_hardware_uri, server_profile_template):
    """Create a server profile from a server profile template.

    :param client: an OneView Client instance
    :param server_profile_name: the name of the new server profile
    :param server_hardware_uri: the server_hardware assigned to server profile
    :param server_profile_template: the server profile template id or uri
    :returns: The new server profile generated with the name and server
              hardware passed on parameters
    :raises HPOneViewException: if the communication with OneView fails

    """
    server_profile = client.server_profile_templates.get_new_profile(
        server_profile_template
    )
    server_profile['name'] = server_profile_name
    server_profile['serverHardwareUri'] = server_hardware_uri
    server_profile['serverProfileTemplateUri'] = ""
    return client.server_profiles.create(server_profile)


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
    server_profile_uri = server_hardware.get('serverProfileUri')
    return (server_profile_uri is not None and
            operation(applied_sp_uri, server_profile_uri))


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

    :param 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(client, node,
                                         predicate, positive, negative)


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

    :param 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(client, node,
                                         predicate, positive, negative)


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

    :param 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)
    try:
        server_hardware = client.server_hardware.get(
            oneview_info.get('server_hardware_uri')
        )
    except client_exception.HPOneViewResourceNotFound 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):
    """Add the applied Server Profile uri to a node.

    :param node: an ironic node object
    :param applied_profile: the server_profile that will be applied to node

    """
    driver_info = node.driver_info
    driver_info['applied_server_profile_uri'] = applied_profile.get('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(client, node,
                                       server_profile_name):
    """Allocate Server Hardware to ironic.

    :param 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(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_uri = oneview_info.get("server_hardware_uri")
        spt_uri = oneview_info.get("server_profile_template_uri")
        server_hardware = client.server_hardware.get(sh_uri)

        # Don't have Server Profile on OneView but has
        # `applied_server_profile_uri` on driver_info
        if not server_hardware.get('serverProfileUri') and applied_sp_uri:
            _del_applied_server_profile_uri_field(node)
            LOG.info(
                "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.get('serverProfileUri') and
            server_hardware.get('serverProfileUri') == applied_sp_uri
        ):
            LOG.info(
                "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 = _create_profile_from_template(
                client, server_profile_name, sh_uri, spt_uri
            )
            _add_applied_server_profile_uri_field(node, applied_profile)

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

        except client_exception.HPOneViewInvalidResource as e:
            LOG.error("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(client, task):
    """Deallocate Server Hardware from ironic.

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

    """
    node = task.node
    if is_node_in_use_by_ironic(client, node):
        oneview_info = common.get_oneview_info(node)
        server_profile_uri = oneview_info.get('applied_server_profile_uri')

        try:
            task.driver.power.set_power_state(task, states.POWER_OFF)
            client.server_profiles.delete(server_profile_uri)
            _del_applied_server_profile_uri_field(node)
            LOG.info("Server Profile %(server_profile_uri)s was deleted "
                     "from node %(node_uuid)s in OneView.",
                     {'server_profile_uri': server_profile_uri,
                      'node_uuid': node.uuid})
        except client_exception.HPOneViewException 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("Cannot deallocate node %(node_uuid)s "
                    "in OneView because it is not in use by "
                    "ironic.", {'node_uuid': node.uuid})