File: shell.py

package info (click to toggle)
python-ironic-inspector-client 5.4.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 592 kB
  • sloc: python: 2,082; makefile: 15; sh: 2
file content (378 lines) | stat: -rw-r--r-- 14,259 bytes parent folder | download | duplicates (5)
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
# 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.

"""OpenStackClient plugin for Ironic Inspector."""

import json
import os
import sys

from cliff import command
from cliff import lister
from cliff import show
import yaml

import ironic_inspector_client
from ironic_inspector_client.common.i18n import _
from ironic_inspector_client import resource as res


API_NAME = 'baremetal_introspection'
API_VERSION_OPTION = 'inspector_api_version'
DEFAULT_API_VERSION = '1'
API_VERSIONS = {
    "1": "ironic_inspector.shell",
}

for mversion in range(ironic_inspector_client.MAX_API_VERSION[1] + 1):
    API_VERSIONS["1.%d" % mversion] = API_VERSIONS["1"]


def make_client(instance):
    url = instance.get_configuration().get('inspector_url')
    if not url:
        url = instance.get_endpoint_for_service_type(
            'baremetal-introspection', interface=instance.interface,
            region_name=instance._region_name
        )
    return ironic_inspector_client.ClientV1(
        inspector_url=url,
        session=instance.session,
        api_version=instance._api_version[API_NAME],
        interface=instance._interface,
        region_name=instance._region_name)


def build_option_parser(parser):
    # TODO(dtantsur): deprecate these options in favor of more generic OS_*
    parser.add_argument('--inspector-api-version',
                        default=(os.environ.get('INSPECTOR_VERSION')
                                 or DEFAULT_API_VERSION),
                        help='inspector API version, only 1 is supported now '
                        '(env: INSPECTOR_VERSION).')
    parser.add_argument('--inspector-url',
                        default=os.environ.get('INSPECTOR_URL'),
                        help='inspector URL, defaults to localhost '
                        '(env: INSPECTOR_URL).')
    return parser


class StartCommand(lister.Lister):
    """Start the introspection."""

    COLUMNS = ('UUID', 'Error')

    def get_parser(self, prog_name):
        parser = super(StartCommand, self).get_parser(prog_name)
        parser.add_argument('node', help='baremetal node UUID(s) or name(s)',
                            nargs='+')
        parser.add_argument('--wait',
                            action='store_true',
                            help='wait for introspection to finish; the result'
                            ' will be displayed in the end')
        parser.add_argument('--check-errors',
                            action='store_true',
                            help='check if errors occurred during the'
                            ' introspection; if any error occurs only the'
                            ' errors are displayed; can only be used with'
                            ' --wait')
        return parser

    def take_action(self, parsed_args):
        if parsed_args.check_errors and not parsed_args.wait:
            raise RuntimeError(
                _("--check-errors can only be used with --wait"))

        client = self.app.client_manager.baremetal_introspection
        for uuid in parsed_args.node:
            client.introspect(uuid)

        if parsed_args.wait:
            print('Waiting for introspection to finish...', file=sys.stderr)
            result = client.wait_for_finish(parsed_args.node)
            result = [(uuid, s.get('error'))
                      for uuid, s in result.items()]
            if parsed_args.check_errors:
                uuids_errors = "\n".join("%s (%s)" % node_info
                                         for node_info in result
                                         if node_info[1] is not None)
                if uuids_errors:
                    raise Exception(
                        _("Introspection failed for some nodes: %s")
                        % uuids_errors)
        else:
            result = []

        return self.COLUMNS, result


class ReprocessCommand(command.Command):
    """Reprocess stored introspection data"""

    def get_parser(self, prog_name):
        parser = super(ReprocessCommand, self).get_parser(prog_name)
        parser.add_argument('node', help='baremetal node UUID or name')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        client.reprocess(parsed_args.node)


class StatusCommand(show.ShowOne):
    """Get introspection status."""
    hidden_status_items = {'links'}

    @classmethod
    def status_attributes(cls, client_item):
        """Get status attributes from an API client dict.

        Filters the status fields according to the cls.hidden_status_items
        :param client_item: an item returned from either the get_status or the
                            list_statuses client method
        :return: introspection status as a list of name, value pairs
        """
        return [item for item in client_item.items()
                if item[0] not in cls.hidden_status_items]

    def get_parser(self, prog_name):
        parser = super(StatusCommand, self).get_parser(prog_name)
        parser.add_argument('node', help='baremetal node UUID or name')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        status = client.get_status(parsed_args.node)
        return zip(*sorted(self.status_attributes(status)))


class StatusListCommand(lister.Lister):
    """List introspection statuses"""

    COLUMNS = ('UUID', 'Started at', 'Finished at', 'Error')
    MAPPING = dict(zip(COLUMNS,
                       ['uuid', 'started_at', 'finished_at', 'error']))

    @classmethod
    def status_row(cls, client_item):
        """Get a row from a client_item.

        The row columns are filtered&sorted according to cls.COLUMNS.

        :param client_item: an item returned from either the get_status or the
                            list_statuses client method.
        :return: a list of client_item attributes as the row
        """
        status = dict(StatusCommand.status_attributes(client_item))
        return tuple(status.get(cls.MAPPING[item]) for item in cls.COLUMNS)

    def get_parser(self, prog_name):
        parser = super(StatusListCommand, self).get_parser(prog_name)
        parser.add_argument('--marker', help='UUID of the last item on the '
                                             'previous page', default=None)
        parser.add_argument('--limit', help='the amount of items to return',
                            type=int, default=None)
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        statuses = client.list_statuses(marker=parsed_args.marker,
                                        limit=parsed_args.limit)
        rows = [self.status_row(status) for status in statuses]
        return self.COLUMNS, rows


class AbortCommand(command.Command):
    """Abort running introspection for node."""

    def get_parser(self, prog_name):
        parser = super(AbortCommand, self).get_parser(prog_name)
        parser.add_argument('node', help='baremetal node UUID or name')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        client.abort(parsed_args.node)


class RuleImportCommand(lister.Lister):
    """Import one or several introspection rules from a JSON/YAML file."""

    COLUMNS = ("UUID", "Description")

    def get_parser(self, prog_name):
        parser = super(RuleImportCommand, self).get_parser(prog_name)
        parser.add_argument('file', help='JSON or YAML file to import, may '
                            'contain one or several rules')
        return parser

    def take_action(self, parsed_args):
        with open(parsed_args.file, 'r') as fp:
            rules = yaml.safe_load(fp)
            if not isinstance(rules, list):
                rules = [rules]
        client = self.app.client_manager.baremetal_introspection
        result = []
        for rule in rules:
            result.append(client.rules.from_json(rule))
        result = [tuple(rule.get(col.lower()) for col in self.COLUMNS)
                  for rule in result]
        return self.COLUMNS, result


class RuleListCommand(lister.Lister):
    """List all introspection rules."""

    COLUMNS = ("UUID", "Description")

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        rules = client.rules.get_all()
        rules = [tuple(rule.get(col.lower()) for col in self.COLUMNS)
                 for rule in rules]
        return self.COLUMNS, rules


class RuleShowCommand(show.ShowOne):
    """Show an introspection rule."""

    def get_parser(self, prog_name):
        parser = super(RuleShowCommand, self).get_parser(prog_name)
        parser.add_argument('uuid', help='rule UUID')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        rule = client.rules.get(parsed_args.uuid)
        del rule['links']
        return self.dict2columns(rule)


class RuleDeleteCommand(command.Command):
    """Delete an introspection rule."""

    def get_parser(self, prog_name):
        parser = super(RuleDeleteCommand, self).get_parser(prog_name)
        parser.add_argument('uuid', help='rule UUID')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        client.rules.delete(parsed_args.uuid)


class RulePurgeCommand(command.Command):
    """Drop all introspection rules."""

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        client.rules.delete_all()


class DataSaveCommand(command.Command):
    """Save or display raw introspection data."""

    def get_parser(self, prog_name):
        parser = super(DataSaveCommand, self).get_parser(prog_name)
        parser.add_argument("--file", metavar="<filename>",
                            help="downloaded introspection data filename "
                            "(default: stdout)")
        parser.add_argument('--unprocessed', action='store_true',
                            help="download the unprocessed data")
        parser.add_argument('node', help='baremetal node UUID or name')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.baremetal_introspection
        data = client.get_data(parsed_args.node,
                               raw=bool(parsed_args.file),
                               processed=not parsed_args.unprocessed)
        if parsed_args.file:
            with open(parsed_args.file, 'wb') as fp:
                fp.write(data)
        else:
            json.dump(data, sys.stdout)


class InterfaceListCommand(lister.Lister):
    """List interface data including attached switch port information."""

    def get_parser(self, prog_name):
        parser = super(InterfaceListCommand, self).get_parser(prog_name)
        parser.add_argument('node_ident', help='baremetal node UUID or name')
        parser.add_argument("--vlan",
                            action='append',
                            default=[], type=int,
                            help="List only interfaces configured "
                            "for this vlan id, can be repeated")
        display_group = parser.add_mutually_exclusive_group()
        display_group.add_argument(
            '--long', dest='detail',
            action='store_true', default=False,
            help="Show detailed information about interfaces.")
        display_group.add_argument(
            '--fields', nargs='+', dest='fields',
            metavar='<field>',
            choices=sorted(res.InterfaceResource(detailed=True).fields),
            help="Display one or more fields.  "
            "Can not be used when '--long' is specified")

        return parser

    def take_action(self, parsed_args):

        client = self.app.client_manager.baremetal_introspection

        # If --long defined, use all fields
        interface_res = res.InterfaceResource(parsed_args.fields,
                                              parsed_args.detail)

        rows = client.get_all_interface_data(parsed_args.node_ident,
                                             interface_res.fields,
                                             vlan=parsed_args.vlan)

        return interface_res.labels, rows


class InterfaceShowCommand(show.ShowOne):
    """Show interface data including attached switch port information."""

    COLUMNS = ("Field", "Value")

    def get_parser(self, prog_name):
        parser = super(InterfaceShowCommand, self).get_parser(prog_name)
        parser.add_argument('node_ident', help='baremetal node UUID or name')
        parser.add_argument('interface', help='interface name')
        parser.add_argument(
            '--fields', nargs='+', dest='fields',
            metavar='<field>',
            choices=sorted(res.InterfaceResource(detailed=True).fields),
            help="Display one or more fields.")

        return parser

    def take_action(self, parsed_args):

        client = self.app.client_manager.baremetal_introspection

        if parsed_args.fields:
            interface_res = res.InterfaceResource(parsed_args.fields)
        else:
            # Show all fields in detailed resource
            interface_res = res.InterfaceResource(detailed=True)

        iface_dict = client.get_interface_data(parsed_args.node_ident,
                                               parsed_args.interface,
                                               interface_res.fields)

        return tuple(zip(*(iface_dict.items())))