summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2009-02-02 18:45:47 +0000
committerNarayan Desai <desai@mcs.anl.gov>2009-02-02 18:45:47 +0000
commitcd0f3c4da901ab9cecc0ff1cb73382df425e2c7b (patch)
tree8e41647527f047c3e77d7cf0606f91d4bd885178
parent67ea84767b1c06c9efd0449f435cae103cb03fb0 (diff)
downloadbcfg2-cd0f3c4da901ab9cecc0ff1cb73382df425e2c7b.tar.gz
bcfg2-cd0f3c4da901ab9cecc0ff1cb73382df425e2c7b.tar.bz2
bcfg2-cd0f3c4da901ab9cecc0ff1cb73382df425e2c7b.zip
new version of BB that works as a connector (for substantial code savings)
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@5056 ce84e21b-d406-0410-9b95-82705330c041
-rw-r--r--src/lib/Server/Plugins/BB.py390
1 files changed, 74 insertions, 316 deletions
diff --git a/src/lib/Server/Plugins/BB.py b/src/lib/Server/Plugins/BB.py
index 1e5166500..1f35fae68 100644
--- a/src/lib/Server/Plugins/BB.py
+++ b/src/lib/Server/Plugins/BB.py
@@ -1,334 +1,92 @@
-'''BB Plugin'''
-
+import lxml.etree
import Bcfg2.Server.Plugin
-import lxml.etree
-import os, fcntl
-from socket import gethostbyname
-from Bcfg2.Server.Plugins.Metadata import MetadataConsistencyError
+import glob
+import os
+import socket
-# map of keywords to profiles
-# probably need a better way to do this
-PROFILE_MAP = {"ubuntu-i386":"compute-node",
- "ubuntu-amd64":"compute-node-amd64",
- "fc6":"fc6-compute-node",
- "peta":"pvfs-server",
- "bbsto":"fileserver",
- "bblogin":"head-node"}
+#manage boot symlinks
+ #add statistics check to do build->boot mods
-DOMAIN_SUFFIX = ".mcs.anl.gov" # default is .mcs.anl.gov
+#map profiles: first array is not empty we replace the -p with a determined profile.
+logger = Bcfg2.Server.Plugin.logger
-PXE_CONFIG = "pxelinux.0" # default is pxelinux.0
+class BBfile(Bcfg2.Server.Plugin.XMLFileBacked):
+ '''Class for bb files'''
+ def Index(self):
+ '''Build data into an xml object'''
-class BB(Bcfg2.Server.Plugins.Metadata.Metadata,
- Bcfg2.Server.Plugin.DirectoryBacked):
- '''BB Plugin handles bb node configuration'''
-
+ try:
+ self.data = lxml.etree.XML(self.data)
+ except lxml.etree.XMLSyntaxError:
+ Bcfg2.Server.Plugin.logger.error("Failed to parse %s" % self.name)
+ return
+ self.tftppath = self.data.get('tftp', '/tftpboot')
+ self.macs = {}
+ self.users = {}
+ self.actions = {}
+ self.bootlinks = []
+
+ for node in self.data.findall('Node'):
+ iface = node.find('Interface')
+ mac = "01-%s" % (iface.get('mac'.replace(':','-').lower()))
+ self.actions[node.get('name')] = node.get('action')
+ self.bootlinks.append(mac, node.get('action'))
+ if iface != None:
+ try:
+ ip = socket.gethostbyname(node.get('name'))
+ except:
+ logger.error("failed host resolution for %s" % node.get('name'))
+
+ self.macs[node.get('name')] = (iface.get('mac'), ip)
+ else:
+ logger.error("%s" % lxml.etree.tostring(node))
+ self.users[node.get('name')] = node.get('user',"").split(':')
+
+ def enforce_bootlinks(self):
+ for mac, target in self.bootlinks:
+ path = self.tftppath + '/' + mac
+ if not os.path.islink(path):
+ logger.error("Boot file %s not a link" % path)
+ if target != os.readlink(path):
+ try:
+ os.unlink(path)
+ os.symlink(target, path)
+ except:
+ logger.error("Failed to modify link %s" % path)
+
+class BBDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
+ __child__ = BBfile
+
+
+class BB(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Connector):
+ '''The BB plugin maps users to machines and metadata to machines'''
name = 'BB'
+ version = '$Revision: $'
experimental = True
- write_to_disk = True
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
- try:
- Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, self.core.fam)
- except OSError, ioerr:
- self.logger.error("Failed to load BB repository from %s" % (self.data))
- self.logger.error(ioerr)
- raise Bcfg2.Server.Plugin.PluginInitError
- Bcfg2.Server.Plugins.Metadata.Metadata.__init__(self, core, datastore, False)
- self.Entries = {'ConfigFile':{'/etc/security/limits.conf':self.gen_limits,
- '/root/.ssh/authorized_keys':self.gen_root_keys,
- '/etc/sudoers':self.gen_sudoers,
- '/etc/dhcp3/dhcpd.conf':self.gen_dhcpd}}
- self.nodes = {}
- self.dhcpd_loaded = False
- self.need_update = False
-
- def viz(self, hosts, bundles, key, colors):
- '''admin mode viz support'''
- groups_tree = lxml.etree.parse(self.data + "/groups.xml")
- groups = groups_tree.getroot()
- categories = {'default':'grey83'}
- instances = {}
- viz_str = ""
- egroups = groups.findall("Group") + groups.findall('.//Groups/Group')
- for group in egroups:
- if not group.get('category') in categories:
- categories[group.get('category')] = colors.pop()
- group.set('color', categories[group.get('category')])
- if None in categories:
- del categories[None]
- if hosts:
- clients = self.clients
- for client, profile in clients.iteritems():
- if profile in instances:
- instances[profile].append(client)
- else:
- instances[profile] = [client]
- for profile, clist in instances.iteritems():
- clist.sort()
- viz_str += '''\t"%s-instances" [ label="%s", shape="record" ];\n''' \
- % (profile, '|'.join(clist))
- viz_str += '''\t"%s-instances" -> "group-%s";\n''' \
- % (profile, profile)
- if bundles:
- bundles = []
- [bundles.append(bund.get('name')) \
- for bund in groups.findall('.//Bundle') \
- if bund.get('name') not in bundles]
+ Bcfg2.Server.Plugin.Connector.__init__(self)
+ self.store = BBDirectoryBacked(self.data, core.fam)
- bundles.sort()
- for bundle in bundles:
- viz_str += '''\t"bundle-%s" [ label="%s", shape="septagon"];\n''' \
- % (bundle, bundle)
- gseen = []
- for group in egroups:
- if group.get('profile', 'false') == 'true':
- style = "filled, bold"
- else:
- style = "filled"
- gseen.append(group.get('name'))
- viz_str += '\t"group-%s" [label="%s", style="%s", fillcolor=%s];\n' % \
- (group.get('name'), group.get('name'), style, group.get('color'))
- if bundles:
- for bundle in group.findall('Bundle'):
- viz_str += '\t"group-%s" -> "bundle-%s";\n' % \
- (group.get('name'), bundle.get('name'))
- gfmt = '\t"group-%s" [label="%s", style="filled", fillcolor="grey83"];\n'
- for group in egroups:
- for parent in group.findall('Group'):
- if parent.get('name') not in gseen:
- viz_str += gfmt % (parent.get('name'), parent.get('name'))
- gseen.append(parent.get("name"))
- viz_str += '\t"group-%s" -> "group-%s" ;\n' % \
- (group.get('name'), parent.get('name'))
- if key:
- for category in categories:
- viz_str += '''\t"''' + category + '''" [label="''' + category + \
- '''", shape="record", style="filled", fillcolor=''' + \
- categories[category] + '''];\n'''
- return viz_str
-
- def remove_client(self, client_name):
- '''Remove client from bb.xml'''
- bb_tree = lxml.etree.parse(self.data + "/bb.xml")
- root = bb_tree.getroot()
- if DOMAIN_SUFFIX in client_name:
- client_name = client_name.split('.')[0]
- if len(root.xpath(".//Node[@name='%s']" % client_name)) != 1:
- self.logger.error("Client \"%s\" does not exist" % client_name)
- raise MetadataConsistencyError
- else:
- root.remove(root.xpath(".//Node[@name='%s']" % client_name)[0])
- self.write_metadata(bb_tree)
+ def get_additional_metadata(self, metadata):
+
+ users = {}
+ for user in self.store.entries['bb.xml'].users.get(metadata.hostname, []):
+ pubkeys = []
+ for fname in glob.glob('/home/%s/.ssh/*.pub'%user):
+ pubkeys.append(open(fname).read())
+
+ users[user] = pubkeys
+
+ return ([],
+ dict([('users', users),
+ ('macs', self.store.entries['bb.xml'].macs)]))
- def add_client(self, client_name, attribs):
- '''Add a client to bb.xml'''
- bb_tree = lxml.etree.parse(self.data + "/bb.xml")
- root = bb_tree.getroot()
- if DOMAIN_SUFFIX in client_name:
- client_name = client_name.split('.')[0]
- if len(root.xpath(".//Node[@name='%s']" % client_name)) != 0:
- self.logger.error("Client \"%s\" already exists" % client_name)
- raise MetadataConsistencyError
- else:
- element = lxml.etree.Element("Client", name=client_name)
- for key, val in attribs.iteritems():
- element.set(key, val)
- root.append(element)
- self.write_metadata(bb_tree)
- def write_metadata(self, tree):
- '''write metadata back to bb.xml'''
- data_file = open(self.data + "/bb.xml","w")
- fd = data_file.fileno()
- while True:
- try:
- fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
- except IOError:
- continue
- else:
- break
- tree.write(data_file)
- fcntl.lockf(fd, fcntl.LOCK_UN)
- data_file.close()
- def gen_dhcpd(self, entry, metadata):
- '''Generate dhcpd.conf to serve to dhcp server'''
- entry.text = self.entries["static.dhcpd.conf"].data
- for host, data in self.nodes.iteritems():
- entry.text += "host %s {\n" % (host + DOMAIN_SUFFIX)
- if 'mac' in data and 'ip' in data:
- entry.text += " hardware ethernet %s;\n" % (data['mac'])
- entry.text += " fixed-address %s;\n" % (data['ip'])
- entry.text += " filename \"%s\";\n}\n" % (PXE_CONFIG)
- else:
- self.logger.error("incomplete client data")
- perms = {'owner':'root', 'group':'root', 'perms':'0600'}
- [entry.attrib.__setitem__(key, value) for (key, value)
- in perms.iteritems()]
- def update_dhcpd(self):
- '''Upadte dhcpd.conf if bcfg2 server is also the bcfg2 server'''
- entry = self.entries["static.dhcpd.conf"].data
- for host, data in self.nodes.iteritems():
- entry += "host %s {\n" % (host + DOMAIN_SUFFIX)
- if 'mac' in data and 'ip' in data:
- entry += " hardware ethernet %s;\n" % (data['mac'])
- entry += " fixed-address %s;\n" % (data['ip'])
- entry += " filename \"%s\";\n}\n" % (PXE_CONFIG)
- else:
- self.logger.error("incomplete client data")
- dhcpd = open("/etc/dhcp3/dhcpd.conf",'w')
- dhcpd.write(entry)
- dhcpd.close()
-
- def gen_root_keys(self, entry, metadata):
- '''Build /root/.ssh/authorized_keys entry'''
- users = self.get_users(metadata)
- rdata = self.entries
- entry.text = "".join([rdata["%s.key" % user].data for user
- in users if ("%s.key" % user) in rdata])
- perms = {'owner':'root', 'group':'root', 'perms':'0600'}
- [entry.attrib.__setitem__(key, value) for (key, value)
- in perms.iteritems()]
-
- def gen_sudoers(self, entry, metadata):
- '''Build /etc/sudoers entry'''
- users = self.get_users(metadata)
- entry.text = self.entries['static.sudoers'].data
- entry.text += "".join(["%s ALL=(ALL) ALL\n" % user
- for user in users])
- perms = {'owner':'root', 'group':'root', 'perms':'0440'}
- [entry.attrib.__setitem__(key, value) for (key, value)
- in perms.iteritems()]
- def gen_limits(self, entry, metadata):
- '''Build /etc/security/limits.conf entry'''
- users = self.get_users(metadata)
- entry.text = self.entries["static.limits.conf"].data
- perms = {'owner':'root', 'group':'root', 'perms':'0600'}
- [entry.attrib.__setitem__(key, value) for (key, value) in perms.iteritems()]
- entry.text += "".join(["%s hard maxlogins 1024\n" % uname for uname in users])
- if "*" not in users:
- entry.text += "* hard maxlogins 0\n"
- def get_users(self, metadata):
- '''Get users associated with a specific host'''
- users = []
- for host, host_dict in self.nodes.iteritems():
- if host == metadata.hostname.split('.')[0]:
- if 'user' in host_dict:
- if host_dict['user'] != "none":
- users.append(host_dict['user'])
- return users
-
- def BuildStructures(self, metadata):
- '''Update build/boot state and create bundle for server'''
- try:
- host_attr = self.nodes[metadata.hostname.split('.')[0]]
- except KeyError:
- self.logger.error("failed to find metadata for host %s"
- % metadata.hostname)
- return []
- bundles = []
- # create symlink and dhcp bundle
- bundle = lxml.etree.Element('Bundle', name='boot-server')
- for host, data in self.nodes.iteritems():
- link = lxml.etree.Element('BoundSymLink')
- link.attrib['name'] = "01-%s" % (data['mac'].replace(':','-').lower())
- link.attrib['to'] = data['action']
- bundle.append(link)
- dhcpd = lxml.etree.Element('BoundConfigFile', name='/etc/dhcp3/dhcpd.conf')
- bundle.append(dhcpd)
- bundles.append(bundle)
- # toggle build/boot in bb.xml
- if host_attr['action'].startswith("build"):
- # make new action string
- action = ""
- if host_attr['action'] == "build":
- action = "boot"
- else:
- action = host_attr['action'].replace("build", "boot", 1)
- # write changes to file
- bb_tree = lxml.etree.parse(self.entries["bb.xml"])
- nodes = bb_tree.getroot().findall(".//Node")
- for node in nodes:
- if node.attrib['name'] == metadata.hostname.split('.')[0]:
- node.attrib['action'] = action
- break
- self.write_metadata(bb_tree)
- return bundles
- def HandleEvent(self, event=None):
- '''Handle events'''
- Bcfg2.Server.Plugin.DirectoryBacked.HandleEvent(self, event)
- # static.dhcpd.conf hack
- if 'static.dhcpd.conf' in self.entries:
- self.dhcpd_loaded = True
- if self.need_update and self.dhcpd_loaded:
- self.update_dhcpd()
- self.need_update = False
- # send events to groups.xml back to Metadata plugin
- if event and "groups.xml" == event.filename:
- Bcfg2.Server.Plugins.Metadata.Metadata.HandleEvent(self, event)
- # handle events to bb.xml
- if event and "bb.xml" == event.filename:
- bb_tree = lxml.etree.parse("%s/%s" % (self.data, event.filename))
- root = bb_tree.getroot()
- elements = root.findall(".//Node")
- for node in elements:
- host = node.attrib['name']
- node_dict = node.attrib
- if node.findall("Interface"):
- iface = node.findall("Interface")[0]
- node_dict['mac'] = iface.attrib['mac']
- if 'ip' in iface.attrib:
- node_dict['ip'] = iface.attrib['ip']
- # populate self.clients dict
- full_hostname = host + DOMAIN_SUFFIX
- profile = ""
- # need to translate image/action into profile name
- if "ubuntu" in node_dict['action']:
- if "amd64" in node_dict['action']:
- profile = PROFILE_MAP["ubuntu-amd64"]
- else:
- profile = PROFILE_MAP["ubuntu-i386"]
- elif "fc6" in node_dict['action']:
- profile = PROFILE_MAP["fc6"]
- elif "peta" in host:
- profile = PROFILE_MAP["peta"]
- elif "bbsto" in host:
- profile = PROFILE_MAP["bbsto"]
- elif "login" in host:
- profile = PROFILE_MAP["bblogin"]
- else:
- profile = "basic"
- self.clients[full_hostname] = profile
- # get ip address from bb.mxl, if available
- if 'ip' in node_dict:
- ip = node_dict['ip']
- self.addresses[ip] = [host]
- else:
- try:
- node_dict['ip'] = gethostbyname(full_hostname)
- except:
- self.logger.error("failed to resolve host %s" % full_hostname)
- self.nodes[host] = node_dict
- # update symlinks and /etc/dhcp3/dhcpd.conf
- if self.write_to_disk:
- if not 'mac' in node_dict:
- self.logger.error("no mac address for %s" % host)
- continue
- mac = node_dict['mac'].replace(':','-').lower()
- linkname = "/tftpboot/pxelinux.cfg/01-%s" % (mac)
- try:
- if os.readlink(linkname) != node_dict['action']:
- os.unlink(linkname)
- os.symlink(node_dict['action'], linkname)
- except OSError:
- self.logger.error("failed to find link for mac address %s" % mac)
- if self.dhcpd_loaded:
- self.update_dhcpd()
- else:
- self.need_update = True