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
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
|
"""
A Cobbler System.
Copyright 2006, Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>
This software may be freely redistributed under the terms of the GNU
general public license.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
import utils
import item
from cexceptions import *
from utils import _
class System(item.Item):
TYPE_NAME = _("system")
COLLECTION_TYPE = "system"
def make_clone(self):
ds = self.to_datastruct()
cloned = System(self.config)
cloned.from_datastruct(ds)
return cloned
def clear(self,is_subobject=False):
self.name = None
self.owners = self.settings.default_ownership
self.profile = None
self.kernel_options = {}
self.ks_meta = {}
self.interfaces = {}
self.netboot_enabled = True
self.depth = 2
self.kickstart = "<<inherit>>" # use value in profile
self.server = "<<inherit>>" # "" (or settings)
self.virt_path = "<<inherit>>" # ""
self.virt_type = "<<inherit>>" # ""
self.virt_cpus = "<<inherit>>" # ""
self.virt_file_size = "<<inherit>>" # ""
self.virt_ram = "<<inherit>>" # ""
self.virt_type = "<<inherit>>" # ""
self.virt_path = "<<inherit>>" # ""
self.virt_bridge = "<<inherit>>" # ""
def delete_interface(self,name):
"""
Used to remove an interface. Not valid for intf0.
"""
if name == "intf0":
raise CX(_("the first interface cannot be deleted"))
if self.interfaces.has_key(name):
del self.interfaces[name]
else:
# NOTE: raising an exception here would break the WebUI as currently implemented
return False
return True
def __get_interface(self,name):
if name not in [ "intf0", "intf1", "intf2", "intf3", "intf4", "intf5", "intf6", "intf7" ]:
raise CX(_("internal error: invalid key for interface lookup or storage, must be 'intfX' where x is 0..7"))
if not self.interfaces.has_key(name):
self.interfaces[name] = {
"mac_address" : "",
"ip_address" : "",
"dhcp_tag" : "",
"subnet" : "",
"gateway" : "",
"hostname" : "",
"virt_bridge" : ""
}
return self.interfaces[name]
def from_datastruct(self,seed_data):
# load datastructures from previous and current versions of cobbler
# and store (in-memory) in the new format.
# (the main complexity here is the migration to NIC data structures)
self.parent = self.load_item(seed_data, 'parent')
self.name = self.load_item(seed_data, 'name')
self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership)
self.profile = self.load_item(seed_data, 'profile')
self.kernel_options = self.load_item(seed_data, 'kernel_options', {})
self.ks_meta = self.load_item(seed_data, 'ks_meta', {})
self.depth = self.load_item(seed_data, 'depth', 2)
self.kickstart = self.load_item(seed_data, 'kickstart', '<<inherit>>')
self.netboot_enabled = self.load_item(seed_data, 'netboot_enabled', True)
self.server = self.load_item(seed_data, 'server', '<<inherit>>')
# virt specific
self.virt_path = self.load_item(seed_data, 'virt_path', '<<inherit>>')
self.virt_type = self.load_item(seed_data, 'virt_type', '<<inherit>>')
self.virt_ram = self.load_item(seed_data,'virt_ram','<<inherit>>')
self.virt_file_size = self.load_item(seed_data,'virt_file_size','<<inherit>>')
self.virt_path = self.load_item(seed_data,'virt_path','<<inherit>>')
self.virt_type = self.load_item(seed_data,'virt_type','<<inherit>>')
self.virt_bridge = self.load_item(seed_data,'virt_bridge','<<inherit>>')
self.virt_cpus = self.load_item(seed_data,'virt_cpus','<<inherit>>')
# backwards compat, these settings are now part of the interfaces data structure
# and will contain data only in upgrade scenarios.
__ip_address = self.load_item(seed_data, 'ip_address', "")
__dhcp_tag = self.load_item(seed_data, 'dhcp_tag', "")
__hostname = self.load_item(seed_data, 'hostname', "")
__mac_address = self.load_item(seed_data, 'mac_address', "")
# now load the new-style interface definition data structure
self.interfaces = self.load_item(seed_data, 'interfaces', {})
# now backfill the interface structure with any old values from
# before the upgrade
if not self.interfaces.has_key("intf0"):
if __hostname != "":
self.set_hostname(__hostname, "intf0")
if __mac_address != "":
self.set_mac_address(__mac_address, "intf0")
if __ip_address != "":
self.set_ip_address(__ip_address, "intf0")
if __dhcp_tag != "":
self.set_dhcp_tag(__dhcp_tag, "intf0")
# backwards compatibility -- convert string entries to dicts for storage
# this allows for better usage from the API.
if self.kernel_options != "<<inherit>>" and type(self.kernel_options) != dict:
self.set_kernel_options(self.kernel_options)
if self.ks_meta != "<<inherit>>" and type(self.ks_meta) != dict:
self.set_ksmeta(self.ks_meta)
# explicitly re-call the set_name function to possibily populate MAC/IP.
self.set_name(self.name)
# coerce types from input file
self.set_netboot_enabled(self.netboot_enabled)
self.set_owners(self.owners)
return self
def get_parent(self):
"""
Return object next highest up the tree.
"""
if self.parent is None or self.parent == '':
return self.config.profiles().find(name=self.profile)
else:
return self.config.systems().find(name=self.parent)
def set_name(self,name):
"""
Set the name. If the name is a MAC or IP, and the first MAC and/or IP is not defined, go ahead
and fill that value in.
"""
intf = self.__get_interface("intf0")
if self.name not in ["",None] and self.parent not in ["",None] and self.name == self.parent:
raise CX(_("self parentage is weird"))
if type(name) != type(""):
raise CX(_("name must be a string"))
for x in name:
if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] :
raise CX(_("invalid characters in name: %s") % x)
if utils.is_mac(name):
if intf["mac_address"] == "":
intf["mac_address"] = name
elif utils.is_ip(name):
if intf["ip_address"] == "":
intf["ip_address"] = name
self.name = name
return True
def set_server(self,server):
"""
If a system can't reach the boot server at the value configured in settings
because it doesn't have the same name on it's subnet this is there for an override.
"""
self.server = server
return True
def get_mac_address(self,interface="intf0"):
"""
Get the mac address, which may be implicit in the object name or explicit with --mac-address.
Use the explicit location first.
"""
intf = self.__get_interface(interface)
if intf["mac_address"] != "":
return intf["mac_address"]
# obsolete, because we should have updated the mac field already with set_name (?)
# elif utils.is_mac(self.name) and interface == "intf0":
# return self.name
else:
return None
def get_ip_address(self,interface="intf0"):
"""
Get the IP address, which may be implicit in the object name or explict with --ip-address.
Use the explicit location first.
"""
intf = self.__get_interface(interface)
if intf["ip_address"] != "":
return intf["ip_address"]
#elif utils.is_ip(self.name) and interface == "intf0":
# return self.name
else:
return None
def is_pxe_supported(self,interface="intf0"):
"""
Can only add system PXE records if a MAC or IP address is available, else it's a koan
only record. Actually Itanium goes beyond all this and needs the IP all of the time
though this is enforced elsewhere (action_sync.py).
"""
if self.name == "default":
return True
for (name,x) in self.interfaces.iteritems():
mac = x.get("mac_address",None)
ip = x.get("ip_address",None)
if mac is not None or ip is not None:
return True
return False
def set_dhcp_tag(self,dhcp_tag,interface="intf0"):
intf = self.__get_interface(interface)
intf["dhcp_tag"] = dhcp_tag
return True
def set_hostname(self,hostname,interface="intf0"):
intf = self.__get_interface(interface)
intf["hostname"] = hostname
return True
def set_ip_address(self,address,interface="intf0"):
"""
Assign a IP or hostname in DHCP when this MAC boots.
Only works if manage_dhcp is set in /etc/cobbler/settings
"""
intf = self.__get_interface(interface)
if address == "" or utils.is_ip(address):
intf["ip_address"] = address
return True
raise CX(_("invalid format for IP address (%s)") % address)
def set_mac_address(self,address,interface="intf0"):
intf = self.__get_interface(interface)
if address == "" or utils.is_mac(address):
intf["mac_address"] = address
return True
raise CX(_("invalid format for MAC address (%s)" % address))
def set_gateway(self,gateway,interface="intf0"):
intf = self.__get_interface(interface)
intf["gateway"] = gateway
return True
def set_subnet(self,subnet,interface="intf0"):
intf = self.__get_interface(interface)
intf["subnet"] = subnet
return True
def set_virt_bridge(self,bridge,interface="intf0"):
intf = self.__get_interface(interface)
intf["virt_bridge"] = bridge
return True
def set_profile(self,profile_name):
"""
Set the system to use a certain named profile. The profile
must have already been loaded into the Profiles collection.
"""
p = self.config.profiles().find(name=profile_name)
if p is not None:
self.profile = profile_name
self.depth = p.depth + 1 # subprofiles have varying depths.
return True
raise CX(_("invalid profile name"))
def set_virt_cpus(self,num):
return utils.set_virt_cpus(self,num)
def set_virt_file_size(self,num):
return utils.set_virt_file_size(self,num)
def set_virt_ram(self,num):
return utils.set_virt_ram(self,num)
def set_virt_type(self,vtype):
return utils.set_virt_type(self,vtype)
def set_virt_path(self,path):
return utils.set_virt_path(self,path)
def set_netboot_enabled(self,netboot_enabled):
"""
If true, allows per-system PXE files to be generated on sync (or add). If false,
these files are not generated, thus eliminating the potential for an infinite install
loop when systems are set to PXE boot first in the boot order. In general, users
who are PXE booting first in the boot order won't create system definitions, so this
feature primarily comes into play for programmatic users of the API, who want to
initially create a system with netboot enabled and then disable it after the system installs,
as triggered by some action in kickstart %post. For this reason, this option is not
surfaced in the CLI, output, or documentation (yet).
Use of this option does not affect the ability to use PXE menus. If an admin has machines
set up to PXE only after local boot fails, this option isn't even relevant.
"""
if str(netboot_enabled).lower() in [ "true", "1", "on", "yes", "y" ]:
# this is a bit lame, though we don't know what the user will enter YAML wise...
self.netboot_enabled = True
else:
self.netboot_enabled = False
return True
def is_valid(self):
"""
A system is valid when it contains a valid name and a profile.
"""
# NOTE: this validation code does not support inheritable distros at this time.
# this is by design as inheritable systems don't make sense.
if self.name is None:
raise CX(_("need to specify a name for this object"))
return False
if self.profile is None:
raise CX(_("need to specify a profile for this system"))
return False
return True
def set_kickstart(self,kickstart):
"""
Sets the kickstart. This must be a NFS, HTTP, or FTP URL.
Or filesystem path. Minor checking of the URL is performed here.
NOTE -- usage of the --kickstart parameter in the profile
is STRONGLY encouraged. This is only for exception cases
where a user already has kickstarts made for each system
and can't leverage templating. Profiles provide an important
abstraction layer -- assigning systems to defined and repeatable
roles.
"""
if kickstart is None or kickstart == "" or kickstart == "delete":
self.kickstart = "<<inherit>>"
return True
if utils.find_kickstart(kickstart):
self.kickstart = kickstart
return True
raise CX(_("kickstart not found"))
def to_datastruct(self):
return {
'name' : self.name,
'kernel_options' : self.kernel_options,
'depth' : self.depth,
'interfaces' : self.interfaces,
'ks_meta' : self.ks_meta,
'kickstart' : self.kickstart,
'netboot_enabled' : self.netboot_enabled,
'owners' : self.owners,
'parent' : self.parent,
'profile' : self.profile,
'server' : self.server,
'virt_cpus' : self.virt_cpus,
'virt_bridge' : self.virt_bridge,
'virt_file_size' : self.virt_file_size,
'virt_path' : self.virt_path,
'virt_ram' : self.virt_ram,
'virt_type' : self.virt_type
}
def printable(self):
buf = _("system : %s\n") % self.name
buf = buf + _("profile : %s\n") % self.profile
buf = buf + _("kernel options : %s\n") % self.kernel_options
buf = buf + _("kickstart : %s\n") % self.kickstart
buf = buf + _("ks metadata : %s\n") % self.ks_meta
buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled
buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("server : %s\n") % self.server
buf = buf + _("virt cpus : %s\n") % self.virt_cpus
buf = buf + _("virt file size : %s\n") % self.virt_file_size
buf = buf + _("virt path : %s\n") % self.virt_path
buf = buf + _("virt ram : %s\n") % self.virt_ram
buf = buf + _("virt type : %s\n") % self.virt_type
counter = 0
for (name,x) in self.interfaces.iteritems():
buf = buf + _("interface : %s\n") % (name)
buf = buf + _(" mac address : %s\n") % x.get("mac_address","")
buf = buf + _(" ip address : %s\n") % x.get("ip_address","")
buf = buf + _(" hostname : %s\n") % x.get("hostname","")
buf = buf + _(" gateway : %s\n") % x.get("gateway","")
buf = buf + _(" subnet : %s\n") % x.get("subnet","")
buf = buf + _(" virt bridge : %s\n") % x.get("virt_bridge","")
buf = buf + _(" dhcp tag : %s\n") % x.get("dhcp_tag","")
counter = counter + 1
return buf
def modify_interface(self, hash):
"""
Used by the WUI to modify an interface more-efficiently
"""
for (key,value) in hash.iteritems():
(field,interface) = key.split("-")
if field == "macaddress" : self.set_mac_address(value, interface)
if field == "ipaddress" : self.set_ip_address(value, interface)
if field == "hostname" : self.set_hostname(value, interface)
if field == "dhcptag" : self.set_dhcp_tag(value, interface)
if field == "subnet" : self.set_subnet(value, interface)
if field == "gateway" : self.set_gateway(value, interface)
if field == "virtbridge" : self.set_virt_bridge(value, interface)
return True
def remote_methods(self):
return {
'name' : self.set_name,
'profile' : self.set_profile,
'kopts' : self.set_kernel_options,
'ksmeta' : self.set_ksmeta,
'hostname' : self.set_hostname,
'kickstart' : self.set_kickstart,
'netboot-enabled' : self.set_netboot_enabled,
'virt-path' : self.set_virt_path,
'virt-type' : self.set_virt_type,
'modify-interface' : self.modify_interface,
'delete-interface' : self.delete_interface,
'virt-path' : self.set_virt_path,
'virt-ram' : self.set_virt_ram,
'virt-type' : self.set_virt_type,
'virt-cpus' : self.set_virt_cpus,
'virt-file-size' : self.set_virt_file_size,
'server' : self.set_server,
'owners' : self.set_owners
}
|