1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import itertools
18
19from vts.runners.host import const
20
21
22class CheckSetupCleanup(object):
23    """An abstract class for defining environment setup job.
24
25    Usually such a job contains check -> setup -> cleanup workflow
26
27    Attributes:
28        to_check: bool, whether or not to check the defined environment
29                  requirement. Default: True
30        to_setup: bool, whether or not to setup the defined environment
31                  requirement. Default: False
32        to_cleanup: bool, whether or not to cleanup the defined environment
33                    requirement if it is set up by this class. Default: False
34        context: ShellEnvironment object that provides AddCleanupJobs and
35                 ExecuteShellCommand method
36        note: string, used by GetNote to generate error message if fail.
37    """
38
39    def __init__(self):
40        self.to_check = True
41        self.to_setup = False
42        self.to_cleanup = False
43        self.context = None
44        self.note = None
45
46    @property
47    def note(self):
48        return self._note
49
50    @note.setter
51    def note(self, note):
52        self._note = note
53
54    @property
55    def context(self):
56        return self._context
57
58    @context.setter
59    def context(self, context):
60        self._context = context
61
62    def Execute(self):
63        """Execute the check, setup, and cleanup.
64
65        Will execute setup and cleanup only if the boolean switches for them
66        are True. It will NOT execute cleanup if check function passes.
67
68           Return:
69               tuple(bool, string), a tuple of True and empty string if success,
70                                    a tuple of False and error message if fail.
71        """
72
73        if self.context is None:
74            self.note = "Error: Environment definition context not set"
75            return False
76
77        if not self.InternalCall(self.ValidateInputs):
78            return False
79
80        check_result = False
81        if self.to_check:
82            check_result = self.InternalCall(self.Check)
83
84        if check_result or not self.to_setup:
85            return check_result
86
87        if self.to_cleanup:
88            self.context.AddCleanupJob(self.InternalCall, self.Cleanup)
89
90        return self.InternalCall(self.Setup)
91
92    def __str__(self):
93        return ("Shell Environment Check Definition Class:{cls} "
94                "Variables: {props}").format(
95                    cls=self.__class__.__name__, props=vars(self))
96
97    def GetNote(self):
98        """Get a string note as error message. Can be override by sub-class"""
99        if not self.note:
100            self.note = "Shell environment definition unsatisfied"
101        return "{}\nat: {}".format(self.note, self)
102
103    def InternalCall(self, method):
104        """Internal method to call sub class inherited methods.
105
106        It call the function, check results, and put failure note if not set
107        """
108        self.note = None
109        success = method()
110        if not success and not self.note:
111            self.note = ("Shell environment definition unsatisfied: step [%s] "
112                         "failed.") % method.__name__
113        return success
114
115    def ValidateInputs(self):
116        """Validate input parameters. Can be override by sub-class
117
118        Return:
119            tuple(bool, string), a tuple of True and empty string if pass,
120                                 a tuple of False and error message if fail.
121        """
122        return True
123
124    def ToListLike(self, obj):
125        """Convert single item to list of single item.
126
127        If input is already a list like object, the same object will
128        be returned.
129        This method is for the convenience of writing child class.
130
131        Arguments:
132            obj: any object
133        """
134        if not self.IsListLike(obj):
135            return [obj]
136        else:
137            return obj
138
139    def IsListLike(self, obj):
140        """Checks whether a object is list-like.
141
142        This method is for the convenience of writing child class.
143        It will check for existence of __iter__ and __getitem__
144        in attributes of the object.
145        String is not considered list-like, tuple is.
146
147        Arguments:
148            obj: any object
149        """
150        return hasattr(obj, '__iter__') and hasattr(obj, '__getitem__')
151
152    def NormalizeInputLists(self, *inputs):
153        """Normalize inputs to lists of same length.
154
155        This method is for the convenience of writing child class.
156        If there are lists in inputs, they should all be of same length;
157        otherwise, None is returned.
158        If there are lists and single items in inputs, single items are
159        duplicated to a list of other list's length.
160        If there are only single items in inputs, they all get converted to
161        single item lists.
162
163        Arguments:
164            inputs: any inputs
165
166        Return:
167            a generator of normalized inputs
168        """
169        # If any of inputs is None or empty, inputs are considered not valid
170        # Definition classes wish to take None input should not use this method
171        if not all(inputs):
172            return None
173
174        lists = filter(self.IsListLike, inputs)
175        if not lists:
176            # All inputs are single items. Convert them to lists
177            return ([i] for i in inputs)
178
179        lengths = set(map(len, lists))
180        if len(lengths) != 1:
181            # lists in inputs have different lengths, cannot normalize
182            return None
183        length = lengths.pop()
184
185        return (i if self.IsListLike(i) else list(itertools.repeat(i, length))
186                for i in inputs)
187
188    def Check(self):
189        """Check function for the class.
190
191        Used to check environment. Can be override by sub-class
192        """
193        self.note = "Check step undefined."
194        return False
195
196    def Setup(self):
197        """Check function for the class.
198
199        Used to setup environment if check fail. Can be override by sub-class
200        """
201        self.note = "Setup step undefined."
202        return False
203
204    def Cleanup(self):
205        """Check function for the class.
206
207        Used to cleanup setup if check fail. Can be override by sub-class
208        """
209        self.note = "Cleanup step undefined."
210        return False
211
212    @property
213    def shell(self):
214        """returns an object that can execute a shell command"""
215        return self.context.shell
216
217    def ExecuteShellCommand(self, cmd):
218        """Execute a shell command or a list of shell commands.
219
220        Args:
221            command: str, the command to execute; Or
222                     list of str, a list of commands to execute
223
224        return:
225            A dictionary containing results in format:
226                {const.STDOUT: [stdouts],
227                 const.STDERR: [stderrs],
228                 const.EXIT_CODE: [exit_codes]}.
229        """
230        return self.shell.Execute(cmd)
231