From fb51c7dfa9c766a35c6a754eb74f9b8f4f1dda10 Mon Sep 17 00:00:00 2001 From: rread Date: Wed, 19 Mar 2003 20:51:37 +0000 Subject: [PATCH] - move the xml/ldap handling class from lconf into an external module - load_ldap.sh - loads a lustre xml config into ldap - lactive - updates failover targets with new active devices --- lustre/utils/Lustre/.cvsignore | 4 + lustre/utils/Lustre/Makefile.am | 2 + lustre/utils/Lustre/__init__.py | 4 + lustre/utils/Lustre/error.py | 6 + lustre/utils/Lustre/lustredb.py | 371 ++++++++++++++++++++++++++++++++++++++++ lustre/utils/lactive | 115 +++++++++++++ lustre/utils/load_ldap.sh | 39 +++++ 7 files changed, 541 insertions(+) create mode 100644 lustre/utils/Lustre/.cvsignore create mode 100644 lustre/utils/Lustre/Makefile.am create mode 100644 lustre/utils/Lustre/__init__.py create mode 100644 lustre/utils/Lustre/error.py create mode 100644 lustre/utils/Lustre/lustredb.py create mode 100644 lustre/utils/lactive create mode 100755 lustre/utils/load_ldap.sh diff --git a/lustre/utils/Lustre/.cvsignore b/lustre/utils/Lustre/.cvsignore new file mode 100644 index 0000000..97e22b9 --- /dev/null +++ b/lustre/utils/Lustre/.cvsignore @@ -0,0 +1,4 @@ +Makefile +Makefile.in +.deps +*.pyc diff --git a/lustre/utils/Lustre/Makefile.am b/lustre/utils/Lustre/Makefile.am new file mode 100644 index 0000000..061adfd --- /dev/null +++ b/lustre/utils/Lustre/Makefile.am @@ -0,0 +1,2 @@ + +pymod_SCRIPTS = __init__.py lustredb.py error.py diff --git a/lustre/utils/Lustre/__init__.py b/lustre/utils/Lustre/__init__.py new file mode 100644 index 0000000..09e98b9 --- /dev/null +++ b/lustre/utils/Lustre/__init__.py @@ -0,0 +1,4 @@ +__all__ = ["lustredb"] + +from lustredb import LustreDB, LustreDB_XML, LustreDB_LDAP +from error import LconfError diff --git a/lustre/utils/Lustre/error.py b/lustre/utils/Lustre/error.py new file mode 100644 index 0000000..ddaaac9 --- /dev/null +++ b/lustre/utils/Lustre/error.py @@ -0,0 +1,6 @@ +import exceptions + +class LconfError (exceptions.Exception): + def __init__(self, args): + self.args = args + diff --git a/lustre/utils/Lustre/lustredb.py b/lustre/utils/Lustre/lustredb.py new file mode 100644 index 0000000..4e0b504 --- /dev/null +++ b/lustre/utils/Lustre/lustredb.py @@ -0,0 +1,371 @@ +import sys, types, string, os +import re, exceptions +import xml.dom.minidom +import Lustre + +# ============================================================ +# XML processing and query + +class LustreDB: + def lookup(self, uuid): + """ lookup returns a new LustreDB instance""" + return self._lookup_by_uuid(uuid) + + def lookup_name(self, name, class_name = ""): + """ lookup returns a new LustreDB instance""" + return self._lookup_by_name(name, class_name) + + def lookup_class(self, class_name): + """ lookup returns a new LustreDB instance""" + return self._lookup_by_class(class_name) + + def get_val(self, tag, default=None): + v = self._get_val(tag) + if v: + return v + if default != None: + return default + return None + + def get_class(self): + return self._get_class() + + def get_val_int(self, tag, default=0): + str = self._get_val(tag) + try: + if str: + return int(str) + return default + except ValueError: + raise LconfError("text value is not integer:", str) + + def get_first_ref(self, tag): + """ Get the first uuidref of the type TAG. Only + one is expected. Returns the uuid.""" + uuids = self._get_refs(tag) + if len(uuids) > 0: + return uuids[0] + return None + + def get_refs(self, tag): + """ Get all the refs of type TAG. Returns list of uuids. """ + uuids = self._get_refs(tag) + return uuids + + def get_all_refs(self): + """ Get all the refs. Returns list of uuids. """ + uuids = self._get_all_refs() + return uuids + + def nid2server(self, nid, net_type): + netlist = self.lookup_class('network') + for net_db in netlist: + if net_db.get_val('nid') == nid and net_db.get_val('nettype') == net_type: + return net_db + return None + + # Find the target_device for target on a node + # node->profiles->device_refs->target + def get_target_device(self, target_uuid, node_name): + node_db = self.lookup_name(node_name) + if not node_db: + return None + prof_list = node_db.get_refs('profile') + for prof_uuid in prof_list: + prof_db = node_db.lookup(prof_uuid) + ref_list = prof_db.get_all_refs() + for ref in ref_list: + dev = self.lookup(ref[1]) + if dev and dev.get_first_ref('target') == target_uuid: + return ref[1] + return None + + # get all network uuids for this node + def get_networks(self): + ret = [] + prof_list = self.get_refs('profile') + for prof_uuid in prof_list: + prof_db = self.lookup(prof_uuid) + net_list = prof_db.get_refs('network') + for net_uuid in net_list: + ret.append(net_uuid) + return ret + + # Change the current active device for a target + def update_active(self, tgtuuid, new_uuid): + self._update_active(tgtuuid, new_uuid) + +class LustreDB_XML(LustreDB): + def __init__(self, dom, root_node): + # init xmlfile + self.dom_node = dom + self.root_node = root_node + + def xmltext(self, dom_node, tag): + list = dom_node.getElementsByTagName(tag) + if len(list) > 0: + dom_node = list[0] + dom_node.normalize() + if dom_node.firstChild: + txt = string.strip(dom_node.firstChild.data) + if txt: + return txt + + def xmlattr(self, dom_node, attr): + return dom_node.getAttribute(attr) + + def _get_val(self, tag): + """a value could be an attribute of the current node + or the text value in a child node""" + ret = self.xmlattr(self.dom_node, tag) + if not ret: + ret = self.xmltext(self.dom_node, tag) + return ret + + def _get_class(self): + return self.dom_node.nodeName + + # + # [(ref_class, ref_uuid),] + def _get_all_refs(self): + list = [] + for n in self.dom_node.childNodes: + if n.nodeType == n.ELEMENT_NODE: + ref_uuid = self.xml_get_ref(n) + ref_class = n.nodeName + list.append((ref_class, ref_uuid)) + + list.sort() + return list + + def _get_refs(self, tag): + """ Get all the refs of type TAG. Returns list of uuids. """ + uuids = [] + refname = '%s_ref' % tag + reflist = self.dom_node.getElementsByTagName(refname) + for r in reflist: + uuids.append(self.xml_get_ref(r)) + return uuids + + def xmllookup_by_uuid(self, dom_node, uuid): + for n in dom_node.childNodes: + if n.nodeType == n.ELEMENT_NODE: + if self.xml_get_uuid(n) == uuid: + return n + else: + n = self.xmllookup_by_uuid(n, uuid) + if n: return n + return None + + def _lookup_by_uuid(self, uuid): + dom = self. xmllookup_by_uuid(self.root_node, uuid) + if dom: + return LustreDB_XML(dom, self.root_node) + + def xmllookup_by_name(self, dom_node, name): + for n in dom_node.childNodes: + if n.nodeType == n.ELEMENT_NODE: + if self.xml_get_name(n) == name: + return n + else: + n = self.xmllookup_by_name(n, name) + if n: return n + return None + + def _lookup_by_name(self, name, class_name): + dom = self.xmllookup_by_name(self.root_node, name) + if dom: + return LustreDB_XML(dom, self.root_node) + + def xmllookup_by_class(self, dom_node, class_name): + return dom_node.getElementsByTagName(class_name) + + def _lookup_by_class(self, class_name): + ret = [] + domlist = self.xmllookup_by_class(self.root_node, class_name) + for node in domlist: + ret.append(LustreDB_XML(node, self.root_node)) + return ret + + def xml_get_name(self, n): + return n.getAttribute('name') + + def getName(self): + return self.xml_get_name(self.dom_node) + + def xml_get_ref(self, n): + return n.getAttribute('uuidref') + + def xml_get_uuid(self, dom_node): + return dom_node.getAttribute('uuid') + + def getUUID(self): + return self.xml_get_uuid(self.dom_node) + + def get_routes(self, type, gw): + """ Return the routes as a list of tuples of the form: + [(type, gw, lo, hi),]""" + res = [] + tbl = self.dom_node.getElementsByTagName('routetbl') + for t in tbl: + routes = t.getElementsByTagName('route') + for r in routes: + net_type = self.xmlattr(r, 'type') + if type != net_type: + lo = self.xmlattr(r, 'lo') + hi = self.xmlattr(r, 'hi') + res.append((type, gw, lo, hi)) + return res + + def get_route_tbl(self): + ret = [] + for r in self.dom_node.getElementsByTagName('route'): + net_type = self.xmlattr(r, 'type') + gw = self.xmlattr(r, 'gw') + lo = self.xmlattr(r, 'lo') + hi = self.xmlattr(r, 'hi') + ret.append((net_type, gw, lo, hi)) + return ret + + def _update_active(self, tgt, new): + raise LconfError("updates not implemented for XML") + +# ================================================================ +# LDAP Support +class LustreDB_LDAP(LustreDB): + def __init__(self, name, attrs, + base = "fs=lustre", + parent = None, + url = "ldap://localhost", + user = "cn=Manager, fs=lustre", + pw = "secret" + ): + self._name = name + self._attrs = attrs + self._base = base + self._parent = parent + self._url = url + self._user = user + self._pw = pw + if parent: + self.l = parent.l + self._base = parent._base + else: + self.open() + + def open(self): + import ldap + try: + self.l = ldap.initialize(self._url) + # Set LDAP protocol version used + self.l.protocol_version=ldap.VERSION3 + # user and pw only needed if modifying db + self.l.bind_s(self._user, self._pw, ldap.AUTH_SIMPLE); + except ldap.LDAPError, e: + raise LconfError(e) + # FIXME, do something useful here + + def close(self): + self.l.unbind_s() + + def ldap_search(self, filter): + """Return list of uuids matching the filter.""" + import ldap + dn = self._base + ret = [] + uuids = [] + try: + for name, attrs in self.l.search_s(dn, ldap.SCOPE_ONELEVEL, + filter, ["uuid"]): + for v in attrs['uuid']: + uuids.append(v) + except ldap.NO_SUCH_OBJECT, e: + pass + except ldap.LDAPError, e: + print e # FIXME: die here? + if len(uuids) > 0: + for uuid in uuids: + ret.append(self._lookup_by_uuid(uuid)) + return ret + + def _lookup_by_name(self, name, class_name): + list = self.ldap_search("lustreName=%s" %(name)) + if len(list) == 1: + return list[0] + return None + + def _lookup_by_class(self, class_name): + return self.ldap_search("objectclass=%s" %(string.upper(class_name))) + + def _lookup_by_uuid(self, uuid): + import ldap + dn = "uuid=%s,%s" % (uuid, self._base) + ret = None + try: + for name, attrs in self.l.search_s(dn, ldap.SCOPE_BASE, + "objectclass=*"): + ret = LustreDB_LDAP(name, attrs, parent = self) + + except ldap.NO_SUCH_OBJECT, e: + pass # just return empty list + except ldap.LDAPError, e: + print e # FIXME: die here? + return ret + + + def _get_val(self, k): + ret = None + if self._attrs.has_key(k): + v = self._attrs[k] + if type(v) == types.ListType: + ret = str(v[0]) + else: + ret = str(v) + return ret + + def _get_class(self): + return string.lower(self._attrs['objectClass'][0]) + + # + # [(ref_class, ref_uuid),] + def _get_all_refs(self): + list = [] + for k in self._attrs.keys(): + if re.search('.*Ref', k): + for uuid in self._attrs[k]: + list.append((k, uuid)) + return list + + def _get_refs(self, tag): + """ Get all the refs of type TAG. Returns list of uuids. """ + uuids = [] + refname = '%sRef' % tag + if self._attrs.has_key(refname): + return self._attrs[refname] + return [] + + def getName(self): + return self._get_val('lustreName') + + def getUUID(self): + return self._get_val('uuid') + + def get_route_tbl(self): + return [] + + def _update_active(self, tgtuuid, newuuid): + """Return list of uuids matching the filter.""" + import ldap + dn = "uuid=%s,%s" %(tgtuuid, self._base) + ret = [] + uuids = [] + try: + print tgtuuid, newuuid + self.l.modify_s(dn, [(ldap.MOD_REPLACE, "activeRef", newuuid)]) + except ldap.NO_SUCH_OBJECT, e: + print e + except ldap.LDAPError, e: + print e # FIXME: die here? + return + + diff --git a/lustre/utils/lactive b/lustre/utils/lactive new file mode 100644 index 0000000..a3a3093 --- /dev/null +++ b/lustre/utils/lactive @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# Copyright (C) 2002 Cluster File Systems, Inc. +# Author: Robert Read +# This file is part of Lustre, http://www.lustre.org. +# +# Lustre is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. +# +# Lustre is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Lustre; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +# Make the new node the active node for all devices it shares with the +# old. The bulk of this code is for figuring out which devices to +# change, and what to change them to. + +# XXX add error checking +# XXX make this code less ugly + +import sys, getopt, types +import string, os +import ldap +import Lustre + + +def usage(): + print """usage: lactive --ldapurl --old --new """ + sys.exit(1) + +def parse_cmdline(argv): + short_opts = "h" + long_opts = ["help", "new=", "old=", "ldapurl=",] + opts = [] + args = [] + config = {} + + try: + opts, args = getopt.getopt(argv, short_opts, long_opts) + except getopt.error: + print "invalid opt" + usage() + + for o, a in opts: + if o in ("-h", "--help"): + usage() + if o in ("--old",): + config['old'] = a + if o in ("--new",): + config['new'] = a + if o in ("--ldapurl",): + config['ldapurl'] = a + + return config + +def get_active(db, tgtuuid): + tgt = db.lookup(tgtuuid) + tgt_dev_uuid =tgt.get_first_ref('active') + return tgt_dev_uuid + +def get_inactive(db, tgtuuid): + prof_list = db.get_refs('profile') + for prof_uuid in prof_list: + prof_db = db.lookup(prof_uuid) + if not prof_db: + panic("profile:", profile, "not found.") + for ref_class, ref_uuid in prof_db.get_all_refs(): + if ref_class in ('osdRef', 'mdsdevRef'): + devdb = db.lookup(ref_uuid) + uuid = devdb.get_first_ref('target') + if tgtuuid == uuid: + return ref_uuid + return None + + +config = parse_cmdline(sys.argv[1:]) + +db = Lustre.LustreDB_LDAP('', {}, base="config=test23,fs=lustre", + url = config['ldapurl']) + +old = db.lookup_name(config['old']) +new = db.lookup_name(config['new']) + +print "old:", old.getUUID() +print "new:", new.getUUID() + +# find all the targets on the failed node and, change the active +# pointers to point to the devices on the new node. +prof_list = old.get_refs('profile') +for prof_uuid in prof_list: + prof_db = db.lookup(prof_uuid) + if not prof_db: + panic("profile:", profile, "not found.") + for ref_class, ref_uuid in prof_db.get_all_refs(): + if ref_class in ('osdRef', 'mdsdevRef'): + devdb = db.lookup(ref_uuid) + tgtuuid = devdb.get_first_ref('target') + active_uuid = get_active(old, tgtuuid) + if ref_uuid != active_uuid: + continue + inactive_uuid = get_inactive(new, tgtuuid) + print ("%s: changing active %s:%s to %s:%s" + % (tgtuuid, config['old'], active_uuid, + config['new'], inactive_uuid)) + db.update_active(tgtuuid, inactive_uuid) + + + diff --git a/lustre/utils/load_ldap.sh b/lustre/utils/load_ldap.sh new file mode 100755 index 0000000..422fe4d --- /dev/null +++ b/lustre/utils/load_ldap.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Load a lustre config xml into an openldap database. +# See https://projects.clusterfs.com/lustre/LustreLDAP +# for more details. +# +# Usage: load_ldap.sh +set -e + +LDAP_BASE=${LDAP_BASE:-fs=lustre} +LDAP_ROOTDN=${LDAP_ROOTDN:-cn=Manager,fs=lustre} +LDAP_PW=${LDAP_PW:-secret} +LDAP_AUTH="-x -D $LDAP_ROOTDN -w $LDAP_PW" +LUSTRE=${LUSTRE:-`dirname $0`/..} + +XML=${XML:-$1} + +if [ -z "$XML" ] || [ ! -r $XML ]; then + echo "usage: $0 xmlfile" + exit 1 +fi + +NAME=`basename $XML .xml` +LDIF=/tmp/$NAME.ldif + +# add the top level record, if needed +ldapsearch $LDAP_AUTH -b $LDAP_BASE > /dev/null 2>&1 || + ldapadd $LDAP_AUTH -f $LUSTRE/conf/top.ldif + +# If this config already exists, then delete it +ldapsearch $LDAP_AUTH -b config=$NAME,$LDAP_BASE > /dev/null 2>&1 && + ldapdelete $LDAP_AUTH -r config=$NAME,$LDAP_BASE + +4xslt --define config=$NAME $XML $LUSTRE/conf/lustre2ldif.xsl > $LDIF + +echo "Loading config to 'config=$NAME,$LDAP_BASE' ..." +ldapadd $LDAP_AUTH -f $LDIF + +rm -f $LDIF -- 1.8.3.1