1 '''
2 Implements the RTSRoot class.
3
4 This file is part of RTSLib.
5 Copyright (c) 2011-2013 by Datera, Inc
6
7 Licensed under the Apache License, Version 2.0 (the "License"); you may
8 not use this file except in compliance with the License. You may obtain
9 a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 License for the specific language governing permissions and limitations
17 under the License.
18 '''
19
20 import re
21 import os
22 import stat
23 import json
24
25 from node import CFSNode
26 from target import Target
27 from fabric import FabricModule
28 from tcm import (StorageObject, FileIOStorageObject, BlockStorageObject,
29 PSCSIStorageObject, RDMCPStorageObject)
30 from utils import RTSLibError, RTSLibBrokenLink, modprobe, mount_configfs
31 from utils import dict_remove, set_attributes
32
33 storageobjects = dict(
34 fileio=FileIOStorageObject,
35 block=BlockStorageObject,
36 pscsi=PSCSIStorageObject,
37 ramdisk=RDMCPStorageObject,
38 )
39
40 default_save_file = "/etc/target/saveconfig.json"
41
43 '''
44 This is an interface to the root of the configFS object tree.
45 Is allows one to start browsing Target and StorageObjects,
46 as well as helper methods to return arbitrary objects from the
47 configFS tree.
48
49 >>> import rtslib.root as root
50 >>> rtsroot = root.RTSRoot()
51 >>> rtsroot.path
52 '/sys/kernel/config/target'
53 >>> rtsroot.exists
54 True
55 >>> rtsroot.targets # doctest: +ELLIPSIS
56 [...]
57 >>> rtsroot.tpgs # doctest: +ELLIPSIS
58 [...]
59 >>> rtsroot.storage_objects # doctest: +ELLIPSIS
60 [...]
61 >>> rtsroot.network_portals # doctest: +ELLIPSIS
62 [...]
63
64 '''
65
66
77
83
88
94
100
106
112
118
124
129
132
133
134
136 '''
137 Returns a dict representing the complete state of the target
138 config, suitable for serialization/deserialization, and then
139 handing to restore().
140 '''
141 d = super(RTSRoot, self).dump()
142 d['storage_objects'] = [so.dump() for so in self.storage_objects]
143 d['targets'] = [t.dump() for t in self.targets]
144 d['fabric_modules'] = [f.dump() for f in self.fabric_modules
145 if f.has_feature("discovery_auth")
146 if f.discovery_enable_auth]
147 return d
148
163
164 - def restore(self, config, clear_existing=False, abort_on_error=False):
165 '''
166 Takes a dict generated by dump() and reconfigures the target to match.
167 Returns list of non-fatal errors that were encountered.
168 '''
169 if clear_existing:
170 self.clear_existing(confirm=True)
171 elif list(self.storage_objects) or list(self.targets):
172 raise RTSLibError("storageobjects or targets present, not restoring." +
173 " Set clear_existing=True?")
174
175 errors = []
176
177 if abort_on_error:
178 def err_func(err_str):
179 raise RTSLibError(err_str)
180 else:
181 def err_func(err_str):
182 errors.append(err_str + ", skipped")
183
184 for index, so in enumerate(config.get('storage_objects', [])):
185 if 'name' not in so:
186 err_func("'name' not defined in storage object %d" % index)
187 continue
188 try:
189 so_cls = storageobjects[so['plugin']]
190 except KeyError:
191 err_func("'plugin' not defined or invalid in storageobject %s" % so['name'])
192 continue
193 kwargs = so.copy()
194 dict_remove(kwargs, ('exists', 'attributes', 'plugin', 'buffered_mode'))
195 try:
196 so_obj = so_cls(**kwargs)
197 except (TypeError, ValueError):
198 err_func("Could not create StorageObject %s" % so['name'])
199 continue
200
201
202 def so_err_func(x):
203 return err_func("Storage Object %s/%s: %s" % (so['plugin'], so['name'], x))
204
205 set_attributes(so_obj, so.get('attributes', {}), so_err_func)
206
207
208 for index, fm in enumerate(config.get('fabric_modules', [])):
209 if 'name' not in fm:
210 err_func("'name' not defined in fabricmodule %d" % index)
211 continue
212 for fm_obj in self.fabric_modules:
213 if fm['name'] == fm_obj.name:
214 fm_obj.setup(fm, err_func)
215 break
216
217 for index, t in enumerate(config.get('targets', [])):
218 if 'wwn' not in t:
219 err_func("'wwn' not defined in target %d" % index)
220 continue
221 if 'fabric' not in t:
222 err_func("target %s missing 'fabric' field" % t['wwn'])
223 continue
224 if t['fabric'] not in (f.name for f in self.fabric_modules):
225 err_func("Unknown fabric '%s'" % t['fabric'])
226 continue
227
228 fm_obj = FabricModule(t['fabric'])
229
230
231 Target.setup(fm_obj, t, err_func)
232
233 return errors
234
236 '''
237 Write the configuration in json format to a file.
238 '''
239 if not save_file:
240 save_file = default_save_file
241
242 with open(save_file+".temp", "w+") as f:
243 os.fchmod(f.fileno(), stat.S_IRUSR | stat.S_IWUSR)
244 f.write(json.dumps(self.dump(), sort_keys=True, indent=2))
245 f.write("\n")
246 os.fsync(f.fileno())
247
248 os.rename(save_file+".temp", save_file)
249
250 - def restore_from_file(self, restore_file=None, clear_existing=True, abort_on_error=False):
251 '''
252 Restore the configuration from a file in json format.
253 Returns a list of non-fatal errors. If abort_on_error is set,
254 it will raise the exception instead of continuing.
255 '''
256 if not restore_file:
257 restore_file = default_save_file
258
259 with open(restore_file, "r") as f:
260 config = json.loads(f.read())
261 return self.restore(config, clear_existing=clear_existing,
262 abort_on_error=abort_on_error)
263
264 targets = property(_list_targets,
265 doc="Get the list of Target objects.")
266 tpgs = property(_list_tpgs,
267 doc="Get the list of all the existing TPG objects.")
268 node_acls = property(_list_node_acls,
269 doc="Get the list of all the existing NodeACL objects.")
270 mapped_luns = property(_list_mapped_luns,
271 doc="Get the list of all the existing MappedLUN objects.")
272 sessions = property(_list_sessions,
273 doc="Get the list of all the existing sessions.")
274 network_portals = property(_list_network_portals,
275 doc="Get the list of all the existing Network Portal objects.")
276 storage_objects = property(_list_storage_objects,
277 doc="Get the list of all the existing Storage objects.")
278 luns = property(_list_luns,
279 doc="Get the list of all existing LUN objects.")
280 fabric_modules = property(_list_fabric_modules,
281 doc="Get the list of all FabricModule objects.")
282
284 '''Run the doctests.'''
285 import doctest
286 doctest.testmod()
287
288 if __name__ == "__main__":
289 _test()
290