xref: /onnv-gate/usr/src/cmd/beadm/BootEnvironment.py (revision 13013:3c7681e3e323)
1# CDDL HEADER START
2#
3# The contents of this file are subject to the terms of the
4# Common Development and Distribution License (the "License").
5# You may not use this file except in compliance with the License.
6#
7# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
8# or http://www.opensolaris.org/os/licensing.
9# See the License for the specific language governing permissions
10# and limitations under the License.
11#
12# When distributing Covered Code, include this CDDL HEADER in each
13# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
14# If applicable, add the following below this CDDL HEADER, with the
15# fields enclosed by brackets "[]" replaced with your own identifying
16# information: Portions Copyright [yyyy] [name of copyright owner]
17#
18# CDDL HEADER END
19#
20
21#
22# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
23#
24
25"""Boot Environment classes used by beadm."""
26
27import datetime
28
29class BootEnvironment:
30    """Boot Environment object that is used by beadm to manage command line
31    options, arguments and the log."""
32
33    def __init__(self):
34        self.trgt_rpool = None
35        self.trgt_be_name_or_snapshot = None
36        self.src_be_name_or_snapshot = None
37        self.properties = {}
38        self.log_id = None
39        self.log = None
40        self.msg_buf = {}
41        self.description = None
42
43class listBootEnvironment:
44    """Base class for beadm list
45    Determine the BE's to display. Prints command output according to option:
46    -d - dataset
47    -s - snapshot
48    -a - all (both dataset and snapshot)
49    <none> - only BE information
50    The -H option produces condensed, parseable output
51        The ';' delimits each field in the output.  BEs with multiple
52        datasets will have multiple lines in the output.
53    """
54
55    def list(self, be_list, ddh, be_name):
56        """ print all output for beadm list command
57        be_list - list of all BEs
58        ddh - if True, Do not Display Headers - just parseable data
59        be_name - user-specified BE, if any
60
61        returns 0 for success
62        side effect: beadm list output printed to stdout
63        """
64
65        #If we're listing Headers, initialize the array holding the
66        #column widths with the header widths themselves.  Later on,
67        #the data in this array will get adjusted as we process actual
68        #row data and find that a piece of data is wider than its
69        #column header.
70        bemaxout = [0 for i in range(len(self.hdr[0]))]
71        if not ddh:
72            #iterate all header rows since their fields may not
73            #be of equal length.
74            for header in self.hdr:
75                icol = 0
76                for hc in header:
77                    if len(hc) + 1 > bemaxout[icol]:
78                        bemaxout[icol] = len(hc) + 1
79                    icol += 1
80
81        #collect BEs
82        beout = {}     #matrix of output text [row][attribute]
83        beoutname = {} #list of BE names [row]
84        be_space = {}  #space used totals for BE [BE name]['space_used','ibei']
85        ibe = 0        #BE index
86        spacecol = -1  #to contain column where space used is displayed
87        for be in be_list:
88            if 'orig_be_name' in be:
89                cur_be = be['orig_be_name']
90                cur_be_obj = be
91
92            #if BE name specified, collect matching BEs
93            if be_name is not None and not self.beMatch(be, be_name):
94                continue
95            attrs = ()
96            #identify BE|dataset|snapshot attributes
97            att = ''
98            for att in ('orig_be_name', 'dataset', 'snap_name'):
99                if att in be and att in self.lattrs:
100                    attrs = self.lattrs[att]
101                    if att == 'orig_be_name':
102                        be_space[cur_be] = {}
103                        be_space[cur_be]['space_used'] = 0
104                        be_space[cur_be]['ibe'] = ibe
105                        if not ddh and len(cur_be) + 1 > bemaxout[0]:
106                            bemaxout[0] = len(cur_be) + 1
107                    break
108            beout[ibe] = {}
109            beoutname[ibe] = cur_be
110
111            icol = 0 #first column
112            for at in attrs:
113                #for option -s, withhold subordinate datasets
114                if self.__class__.__name__ == 'SnapshotList' and \
115                    att == 'snap_name' and  'snap_name' in be and \
116                    '/' in be[att]:
117                    break
118                #convert output to readable format and save
119                save = self.getAttr(at, be, ddh, cur_be_obj)
120                beout[ibe][at] = save
121                #maintain maximum column widths
122                if not ddh and len(save) + 1 > bemaxout[icol]:
123                    bemaxout[icol] = len(save) + 1
124                #sum all snapshots for BE
125                if at == 'space_used' and 'space_used' in be:
126                    spacecol = icol
127                icol += 1 #next column
128            ibe += 1
129            if 'space_used' in be:
130                #sum all snapshots and datasets for BE in 'beadm list'
131                if isinstance(self, BEList):
132                    be_space[cur_be]['space_used'] += be.get('space_used')
133                elif cur_be in be_space and \
134                    ('space_used' not in be_space[cur_be] or
135                        be_space[cur_be]['space_used'] == 0):
136                    #list space used separately for other options
137                    be_space[cur_be]['space_used'] = be.get('space_used')
138
139        #output format total lengths for each BE with any snapshots
140        for cur_be in be_space:
141            save = self.getSpaceValue(be_space[cur_be]['space_used'], ddh)
142            ibe = be_space[cur_be]['ibe']
143            beout[ibe]['space_used'] = save
144            #expand column if widest column entry
145            if (spacecol != -1) and \
146               (not ddh and len(save) + 1 > bemaxout[spacecol]):
147                bemaxout[spacecol] = len(save) + 1
148
149        #print headers in columns
150        if not ddh:
151            for header in self.hdr:
152                outstr = ''
153                for icol in range(len(header)):
154                    outstr += header[icol].ljust(bemaxout[icol])
155                if outstr != '':
156                    print outstr
157
158        #print collected output in columns
159        outstr = ''
160        prev_be = None
161        cur_be = None
162        for ibe in beout: #index output matrix
163            if beoutname[ibe] != None:
164                cur_be = beoutname[ibe]
165            #find attributes for BE type
166            curtype = None
167            for att in ('orig_be_name', 'dataset', 'snap_name'):
168                if att in beout[ibe]:
169                    attrs = self.lattrs[att]
170                    curtype = att
171                    break
172
173            if curtype == None: #default to BE
174                curtype = 'orig_be_name'
175                if 'orig_be_name' in self.lattrs:
176                    attrs = self.lattrs['orig_be_name']
177                else: attrs = ()
178
179            if not ddh:
180                if prev_be != cur_be and cur_be != None:
181                    #for -d,-s,-a, print BE alone on line
182                    if self.__class__.__name__ != 'BEList':
183                        print cur_be
184                    prev_be = cur_be
185
186            #print for one BE/snapshot/dataset
187            icol = 0 #first column
188
189            #if this is a 'dataset' or 'snap_name', start line with BE
190            #name token
191            if ddh and curtype != 'orig_be_name':
192                outstr = cur_be
193
194            for at in attrs: #for each attribute specified in table
195                if ddh: #add separators for parsing
196                    if outstr != '':
197                        outstr += ';' #attribute separator
198                    if at in beout[ibe] and beout[ibe][at] != '-' and \
199                        beout[ibe][at] != '':
200                        outstr += beout[ibe][at]
201                else: #append text justified in column
202                    if at in beout[ibe]:
203                        outstr += beout[ibe][at].ljust(bemaxout[icol])
204                icol += 1 #next column
205
206            if outstr != '':
207                print outstr
208            outstr = ''
209
210        return 0
211
212    def beMatch(self, be, be_name):
213        """find match on user-specified BE."""
214
215        if 'orig_be_name' in be:
216            return be.get('orig_be_name') == be_name
217        if 'dataset' in be:
218            if be.get('dataset') == be_name:
219                return True
220            out = be.get('dataset').split("/")
221            return out[0] == be_name
222        if 'snap_name' in be:
223            if be.get('snap_name') == be_name:
224                return True
225            out = be.get('snap_name').split('@')
226            if out[0] == be_name:
227                return True
228            out = be.get('snap_name').split('/')
229            return out[0] == be_name
230        return False
231
232    def getAttr(self, at, be, ddh, beobj):
233        """
234        Extract information by attribute and format for printing
235        returns '?' if normally present attribute not found - error.
236        """
237        if at == 'blank':
238            return ' '
239        if at == 'dash':
240            return '-'
241        if at == 'orig_be_name':
242            if at not in be:
243                return '-'
244            ret = be[at]
245            if ddh or self.__class__.__name__ == 'BEList':
246                return ret
247            return '   ' + ret #indent
248        if at == 'snap_name':
249            if at not in be:
250                return '-'
251            if self.__class__.__name__ == 'CompleteList':
252                ret = self.prependRootDS(be[at], beobj)
253            else:
254                ret = be[at]
255            if ddh:
256                return ret
257            return '   ' + ret #indent
258        if at == 'dataset':
259            if at not in be:
260                return '-'
261            if self.__class__.__name__ == 'DatasetList' or \
262               self.__class__.__name__ == 'CompleteList':
263                ret = self.prependRootDS(be[at], beobj)
264            else:
265                ret = be[at]
266            if ddh:
267                return ret
268            return '   ' + ret #indent
269        if at == 'active':
270            if at not in be:
271                return '-'
272            ret = ''
273            if 'active' in be and be['active']:
274                ret += 'N'
275            if 'active_boot' in be and be['active_boot']:
276                ret += 'R'
277            if ret == '':
278                return '-'
279            return ret
280        if at == 'mountpoint':
281            if at not in be:
282                return '-'
283            if 'mounted' not in be or not be['mounted']:
284                return '-'
285            return be[at]
286        if at == 'space_used':
287            if at not in be:
288                return '0'
289            return self.getSpaceValue(be[at], ddh)
290        if at == 'mounted':
291            if at not in be:
292                return '-'
293            return be[at]
294        if at == 'date':
295            if at not in be:
296                return '?'
297            if ddh:
298                return str(be[at]) #timestamp in seconds
299            sec = str(datetime.datetime.fromtimestamp(be[at]))
300            return sec[0:len(sec)-3] #trim seconds
301        if at == 'policy':
302            if at not in be:
303                return '?'
304            return be[at]
305        if at == 'root_ds':
306            if at not in be:
307                return '?'
308            if ddh or self.__class__.__name__ == 'BEList':
309                return be[at]
310            return '   ' + be[at]
311        if at == 'uuid_str':
312            if at not in be:
313                return '-'
314            return be[at]
315        #default case - no match on attribute
316        return be[at]
317
318    def getSpaceValue(self, num, ddh):
319        """Readable formatting for disk space size."""
320
321        if ddh:
322            return str(num) #return size in bytes as string
323
324        kilo = 1024.0
325        mega = 1048576.0
326        giga = 1073741824.0
327        tera = 1099511627776.0
328
329        if num == None:
330            return '0'
331        if num < kilo:
332            return str(num) + 'B'
333        if num < mega:
334            return str('%.1f' % (num / kilo)) + 'K'
335        if num < giga:
336            return str('%.2f' % (num / mega)) + 'M'
337        if num < tera:
338            return str('%.2f' % (num / giga)) + 'G'
339        return str('%.2f' % (num / tera)) + 'T'
340
341    def prependRootDS(self, val, beobj):
342        """Prepend root dataset name with BE name stripped."""
343
344        root_ds = beobj.get('root_ds')
345        return root_ds[0:root_ds.rfind('/')+1] + val
346
347
348"""Top level "beadm list" derived classes defined here.
349        Only table definition is done here - all methods are in the base class.
350        Tables driving list:
351                hdr - list of text to output for each column
352                lattrs - dictionary of attributes
353                        Each entry specifies either BE, dataset, snapshot with
354                        an attribute key:
355                                orig_be_name - for BEs
356                                dataset - for datasets
357                                snap_name - for snapshots
358                        Each list item in entry indicates specific datum for
359                        column
360                Number of hdr columns must equal number of lattrs entries
361                        unless ddh (dontDisplayHeaders) is true.
362"""
363class BEList(listBootEnvironment):
364    """specify header and attribute information for BE-only output"""
365
366    def __init__(self, ddh):
367        """Init function for the class."""
368        self.hdr = \
369            ('BE','Active','Mountpoint','Space','Policy','Created'), \
370            ('--','------','----------','-----','------','-------')
371        if ddh:
372            self.lattrs = {'orig_be_name':('orig_be_name', 'uuid_str',
373                           'active', 'mountpoint', 'space_used', 'policy',
374                           'date')}
375        else:
376            self.lattrs = {'orig_be_name':('orig_be_name', 'active',
377                           'mountpoint', 'space_used', 'policy', 'date')}
378
379class DatasetList(listBootEnvironment):
380    """
381    specify header and attribute information for dataset output,
382    -d option
383    """
384    def __init__(self, ddh):
385        """Init function for the class."""
386
387        self.hdr = \
388            ('BE/Dataset','Active','Mountpoint','Space','Policy','Created'), \
389            ('----------','------','----------','-----','------','-------')
390        if ddh:
391            self.lattrs = { \
392                'orig_be_name':('orig_be_name', 'root_ds', 'active',
393                'mountpoint', 'space_used', 'policy', 'date'), \
394                'dataset':('dataset', 'dash', 'mountpoint', 'space_used',
395                'policy', 'date')}
396        else:
397            self.lattrs = { \
398                'orig_be_name':('root_ds', 'active', 'mountpoint',
399                'space_used', 'policy', 'date'), \
400                'dataset':('dataset', 'dash', 'mountpoint', 'space_used',
401                'policy', 'date')}
402
403class SnapshotList(listBootEnvironment):
404    """
405    specify header and attribute information for snapshot output,
406    -s option
407    """
408    def __init__(self, ddh):
409        """Init function for the class."""
410
411        self.hdr = \
412            ('BE/Snapshot','Space','Policy','Created'), \
413            ('-----------','-----','------','-------')
414        self.lattrs = {'snap_name':('snap_name', 'space_used', 'policy',
415                                    'date')}
416
417class CompleteList(listBootEnvironment):
418    """
419    specify header and attribute information for BE and/or dataset and/or
420    snapshot output,
421    -a or -ds options
422    """
423    def __init__(self, ddh):
424        """Init function for the class."""
425
426        self.hdr = \
427    ('BE/Dataset/Snapshot','Active','Mountpoint','Space','Policy','Created'), \
428    ('-------------------','------','----------','-----','------','-------')
429        if ddh:
430            self.lattrs = { \
431                'orig_be_name':('orig_be_name', 'root_ds', 'active',
432                'mountpoint', 'space_used', 'policy', 'date'),
433                'dataset':('dataset', 'dash', 'mountpoint', 'space_used',
434                'policy', 'date'),
435                'snap_name':('snap_name', 'dash', 'dash', 'space_used',
436                'policy', 'date')}
437        else:
438            self.lattrs = { \
439                'orig_be_name':('root_ds', 'active', 'mountpoint',
440                                'space_used', 'policy', 'date'), \
441                'dataset':('dataset', 'dash', 'mountpoint', 'space_used',
442                'policy', 'date'),
443                'snap_name':('snap_name', 'dash', 'dash', 'space_used',
444                'policy', 'date')}
445