1# Copyright (c) 2017 Veritas Technologies LLC.  All rights reserved.
2#
3#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4#    not use this file except in compliance with the License. You may obtain
5#    a copy of the License at
6#
7#         http://www.apache.org/licenses/LICENSE-2.0
8#
9#    Unless required by applicable law or agreed to in writing, software
10#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12#    License for the specific language governing permissions and limitations
13#    under the License.
14
15import json
16import uuid
17
18from oslo_concurrency import processutils as putils
19from oslo_log import log as logging
20from oslo_utils import excutils
21import six
22
23from cinder import exception
24from cinder import utils
25from cinder.volume.drivers.veritas import hs_constants as constants
26
27LOG = logging.getLogger(__name__)
28
29
30def _populate_message_body(kwargs):
31    message_body = {}
32    # Build message body from kwargs
33    for key, value in kwargs.items():
34        if value is not None:
35            message_body[key] = value
36
37    return message_body
38
39
40def generate_routingkey():
41    return six.text_type(uuid.uuid1())
42
43
44def get_guid_with_curly_brackets(guid):
45    return "{%s}" % guid if guid else guid
46
47
48def get_hyperscale_image_id():
49    return "{%s}" % uuid.uuid1()
50
51
52def get_hyperscale_version():
53
54    version = None
55    cmd_err = None
56    try:
57        cmd_arg = {'operation': 'version'}
58        # create a json for cmd argument
59        cmdarg_json = json.dumps(cmd_arg)
60
61        # call hscli for version
62        (cmd_out, cmd_err) = hsexecute(cmdarg_json)
63
64        # cmd_err should be None in case of successful execution of cmd
65        if not cmd_err:
66            processed_output = process_cmd_out(cmd_out)
67            version = processed_output.get('payload')
68        else:
69            LOG.error("Error %s in getting hyperscale version",
70                      cmd_err)
71            raise exception.ErrorInHyperScaleVersion(cmd_err=cmd_err)
72    except (exception.UnableToExecuteHyperScaleCmd,
73            exception.UnableToProcessHyperScaleCmdOutput):
74        LOG.error("Exception in running the command for version",
75                  exc_info=True)
76        raise exception.UnableToExecuteHyperScaleCmd(message="version")
77
78    return version
79
80
81def get_datanode_id():
82
83    dnid = None
84    cmd_out = None
85    cmd_err = None
86    try:
87        cmd_arg = {'operation': 'get_datanode_id'}
88        # create a json for cmd argument
89        cmdarg_json = json.dumps(cmd_arg)
90
91        # call hscli for get_datanode_id
92        (cmd_out, cmd_err) = hsexecute(cmdarg_json)
93
94        # cmd_err should be None in case of successful execution of cmd
95        if not cmd_err:
96            processed_output = process_cmd_out(cmd_out)
97            dnid = processed_output.get('payload')
98        else:
99            LOG.error("Error %s in getting datanode hypervisor id",
100                      cmd_err)
101            raise exception.UnableToExecuteHyperScaleCmd(
102                message=cmdarg_json)
103    except exception.UnableToExecuteHyperScaleCmd:
104        with excutils.save_and_reraise_exception():
105            LOG.debug("Unable to execute get_datanode_id", exc_info=True)
106
107    except exception.UnableToProcessHyperScaleCmdOutput:
108        with excutils.save_and_reraise_exception():
109            LOG.debug("Unable to process get_datanode_id output",
110                      exc_info=True)
111    return dnid
112
113
114def episodic_snap(meta):
115
116    cmd_out = None
117    cmd_err = None
118    out_meta = None
119    try:
120        cmd_arg = {}
121        cmd_arg['operation'] = 'episodic_snap'
122        cmd_arg['metadata'] = meta
123        # create a json for cmd argument
124        cmdarg_json = json.dumps(cmd_arg)
125
126        # call hscli for episodic_snap
127        (cmd_out, cmd_err) = hsexecute(cmdarg_json)
128
129        # cmd_err should be None in case of successful execution of cmd
130        if not cmd_err:
131            processed_output = process_cmd_out(cmd_out)
132            out_meta = processed_output.get('payload')
133        else:
134            LOG.error("Error %s in processing episodic_snap",
135                      cmd_err)
136            raise exception.UnableToExecuteHyperScaleCmd(
137                message=cmdarg_json)
138    except exception.UnableToExecuteHyperScaleCmd:
139        with excutils.save_and_reraise_exception():
140            LOG.debug("Unable to execute episodic_snap", exc_info=True)
141
142    except exception.UnableToProcessHyperScaleCmdOutput:
143        with excutils.save_and_reraise_exception():
144            LOG.debug("Unable to process episodic_snap output",
145                      exc_info=True)
146    return out_meta
147
148
149def get_image_path(image_id, op_type='image'):
150
151    cmd_out = None
152    cmd_err = None
153    image_path = None
154    try:
155        cmd_arg = {}
156        if op_type == 'image':
157            cmd_arg['operation'] = 'get_image_path'
158        elif op_type == 'volume':
159            cmd_arg['operation'] = 'get_volume_path'
160        cmd_arg['image_id'] = image_id
161        # create a json for cmd argument
162        cmdarg_json = json.dumps(cmd_arg)
163
164        # call hscli for get_image_path
165        (cmd_out, cmd_err) = hsexecute(cmdarg_json)
166
167        # cmd_err should be None in case of successful execution of cmd
168        if not cmd_err:
169            processed_output = process_cmd_out(cmd_out)
170            image_path = processed_output.get('payload')
171        else:
172            LOG.error("Error %s in processing get_image_path",
173                      cmd_err)
174            raise exception.UnableToExecuteHyperScaleCmd(
175                message=cmdarg_json)
176    except exception.UnableToExecuteHyperScaleCmd:
177        with excutils.save_and_reraise_exception():
178            LOG.debug("Unable to execute get_image_path", exc_info=True)
179
180    except exception.UnableToProcessHyperScaleCmdOutput:
181        with excutils.save_and_reraise_exception():
182            LOG.debug("Unable to process get_image_path output",
183                      exc_info=True)
184    return image_path
185
186
187def update_image(image_path, volume_id, hs_img_id):
188    cmd_out = None
189    cmd_err = None
190    output = None
191    try:
192        cmd_arg = {}
193        cmd_arg['operation'] = 'update_image'
194        cmd_arg['image_path'] = image_path
195        cmd_arg['volume_id'] = volume_id
196        cmd_arg['hs_image_id'] = hs_img_id
197        # create a json for cmd argument
198        cmdarg_json = json.dumps(cmd_arg)
199
200        (cmd_out, cmd_err) = hsexecute(cmdarg_json)
201
202        # cmd_err should be None in case of successful execution of cmd
203        if not cmd_err:
204            output = process_cmd_out(cmd_out)
205        else:
206            LOG.error("Error %s in execution of update_image",
207                      cmd_err)
208            raise exception.UnableToExecuteHyperScaleCmd(
209                message=cmdarg_json)
210    except exception.UnableToExecuteHyperScaleCmd:
211        with excutils.save_and_reraise_exception():
212            LOG.debug("Unable to execute update_image", exc_info=True)
213
214    except exception.UnableToProcessHyperScaleCmdOutput:
215        with excutils.save_and_reraise_exception():
216            LOG.debug("Unable to process update_image output",
217                      exc_info=True)
218    return output
219
220
221def hsexecute(cmdarg_json):
222
223    cmd_out = None
224    cmd_err = None
225    try:
226        # call hyperscale cli
227        (cmd_out, cmd_err) = utils.execute("hscli",
228                                           cmdarg_json,
229                                           run_as_root=True)
230    except (putils.UnknownArgumentError, putils.ProcessExecutionError,
231            exception.ErrorInParsingArguments, OSError):
232        LOG.error("Exception in running the command for %s",
233                  cmdarg_json,
234                  exc_info=True)
235        raise exception.UnableToExecuteHyperScaleCmd(message=cmdarg_json)
236
237    except Exception:
238        LOG.error("Internal exception in cmd for %s", cmdarg_json,
239                  exc_info=True)
240        raise exception.UnableToExecuteHyperScaleCmd(message=cmdarg_json)
241
242    return (cmd_out, cmd_err)
243
244
245def process_cmd_out(cmd_out):
246    """Process the cmd output."""
247
248    output = None
249
250    try:
251        # get the python object from the cmd_out
252        output = json.loads(cmd_out)
253        error_code = output.get('err_code')
254        if error_code:
255            error_message = output.get('err_msg')
256            operation = output.get('token')
257            LOG.error("Failed to perform %(operation)s with error code"
258                      " %(err_code)s, error message is %(err_msg)s",
259                      {"operation": operation,
260                       "err_code": error_code,
261                       "err_msg": error_message})
262    except ValueError:
263        raise exception.UnableToProcessHyperScaleCmdOutput(cmd_out=cmd_out)
264
265    return output
266
267
268def check_for_setup_error():
269    return True
270
271
272def get_configuration(persona):
273    """Get required configuration from controller."""
274
275    msg_body = {'persona': persona}
276    configuration = None
277    try:
278        cmd_out, cmd_error = message_controller(
279            constants.HS_CONTROLLER_EXCH,
280            'hyperscale.controller.get.configuration',
281            **msg_body)
282        LOG.debug("Response Message from Controller: %s", cmd_out)
283        payload = cmd_out.get('payload')
284        configuration = payload.get('config_data')
285
286    except (exception.ErrorInSendingMsg,
287            exception.UnableToExecuteHyperScaleCmd,
288            exception.UnableToProcessHyperScaleCmdOutput):
289            LOG.exception("Failed to get configuration from controller")
290            raise exception.ErrorInFetchingConfiguration(persona=persona)
291
292    return configuration
293
294
295def _send_message(exchange, routing_key, message_token, **kwargs):
296    """Send message to specified node."""
297
298    cmd_out = None
299    cmd_err = None
300    processed_output = None
301    msg = None
302    try:
303        LOG.debug("Sending message: %s", message_token)
304
305        # Build message from kwargs
306        message_body = _populate_message_body(kwargs)
307        cmd_arg = {}
308        cmd_arg["operation"] = "message"
309        cmd_arg["msg_body"] = message_body
310        cmd_arg["msg_token"] = message_token
311        # exchange name
312        cmd_arg["exchange_name"] = exchange
313        # routing key
314        cmd_arg["routing_key"] = routing_key
315        # create a json for cmd argument
316        cmdarg_json = json.dumps(cmd_arg)
317
318        (cmd_out, cmd_err) = hsexecute(cmdarg_json)
319
320        # cmd_err should be none in case of successful execution of cmd
321        if cmd_err:
322            LOG.debug("Sending message failed. Error %s", cmd_err)
323            raise exception.ErrorInSendingMsg(cmd_err=cmd_err)
324        else:
325            processed_output = process_cmd_out(cmd_out)
326
327    except exception.UnableToExecuteHyperScaleCmd:
328        with excutils.save_and_reraise_exception():
329            msg = ("Unable to execute HyperScale command for %(cmd)s"
330                   " to exchange %(exch)s with key %(rt_key)s")
331            LOG.debug(msg, {"cmd": message_token,
332                            "exch": exchange,
333                            "rt_key": routing_key},
334                      exc_info=True)
335
336    except exception.UnableToProcessHyperScaleCmdOutput:
337        with excutils.save_and_reraise_exception():
338            msg = ("Unable to process msg %(message)s"
339                   " to exchange %(exch)s with key %(rt_key)s")
340            LOG.debug(msg, {"message": message_token,
341                            "exch": exchange,
342                            "rt_key": routing_key})
343
344    return (processed_output, cmd_err)
345
346
347def message_compute_plane(routing_key, message_token, **kwargs):
348    """Send message to compute plane."""
349
350    LOG.debug("Sending message to compute plane")
351
352    return _send_message(constants.HS_COMPUTE_EXCH,
353                         routing_key,
354                         message_token,
355                         **kwargs)
356
357
358def message_data_plane(routing_key, message_token, **kwargs):
359    """Send message to data node."""
360
361    LOG.debug("Sending message to data plane")
362
363    return _send_message(constants.HS_DATANODE_EXCH,
364                         routing_key,
365                         message_token,
366                         **kwargs)
367
368
369def message_controller(routing_key, message_token, **kwargs):
370    """Send message to controller."""
371
372    LOG.debug("Sending message to controller")
373
374    return _send_message(constants.HS_CONTROLLER_EXCH,
375                         routing_key,
376                         message_token,
377                         **kwargs)
378