[Yum-devel] Service discovery plugin
Mihai Ibanescu
misa+yum at redhat.com
Thu May 18 16:40:01 UTC 2006
Hello,
Attached are the 2 files that are part of the service discovery plugin.
Some notes:
- you will need the python-adns package from Extras 5 (more specifically,
version 1.1.0-3.fc5 or newer). This is because previous versions of
python-adns did not have support for SRV record retrieval. Release 3 has the
patch that I sent upstream to Andy Dustman, but I have seen zero reply from
him so far).
- servicedisc.py (the plugin) assumes that zeroconf.py lives in yum's code
directory. This is probably not right, but can be fixed afterwards. Problem
is, I would not put zeroconf.py in /usr/lib/yum-plugins because it's not a
plugin, it's a support library. It may make sense to package zeroconf.py as
a separate rpm - but for prototyping purposes we can deploy in
/usr/lib/python2.4/site-packages/yum
- if python-adns is not installed, the plugin should not raise exceptions (but
it won't do anything useful either).
- read the notes at the beginning of servicedisc.py for how to set up DNS in
order for this to work.
I can bore you with details about some of the comments in the code if you
care.
Please send flames my way :-)
Misa
References:
DNS-SD ietf draft: http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
Setting up unicast DNS to support DNS-SD:
http://www.dns-sd.org/ServerStaticSetup.html
DNS-SD service types: http://www.dns-sd.org/ServiceTypes.html
DNS SRV records RFC: http://www.ietf.org/rfc/rfc2782.txt
-------------- next part --------------
#!/usr/bin/env python
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# (C) Copyright 2006 Red Hat, Inc.
# Author: Mihai Ibanescu <misa at redhat.com>
"""
Version: 0.1
A plugin for the Yellowdog Updater Modified which tries to discover mirror
servers that are advertised over DNS
For each configured repository, the plugin will make a DNS query for a SRV
record with the name <repo-id>._yum._tcp
If such a resource exists, it will be preferred over the 'baseurl' and
'mirrorlist' settings in the repository's configuration section.
If the 'strict' option is set in the plugin's configuration file, then
_only_ the discovered URLs will be used (baseurl and mirrorlist are ignored).
To install this plugin, just drop it into /usr/lib/yum-plugins, and
make sure you have 'plugins=1' in your /etc/yum.conf.
# Configuration Options
# /etc/yum/pluginconf.d/servicedisc.conf:
[main]
enabled=1
# strict=1 # use _only_ the urls defined
# debuglevel=1
To configure DNS, add the following to your DNS zone file. This assumes you do
not have anything else configured to use DNS-SD.
; This domain is browsable
b._dns-sd._udp IN PTR @
lb._dns-sd._udp IN PTR @
; Available services through this domain
_services._dns-sd._udp IN PTR _yum._tcp
; Here we ennumerate service instances for each service
core._yum._tcp IN SRV 1 0 80 download.fedora.mirrors.example.com.
IN TXT "txtvers=1.0" "path=/pub/fedora/linux/core/$releasever/$basearch/os"
updates._yum._tcp IN SRV 1 0 80 download.fedora.mirrors.example.com.
IN TXT "txtvers=1.0" "path=/pub/fedora/linux/core/updates/$releasever/$basearch"
extras._yum._tcp IN SRV 1 0 80 download.fedora.mirrors.example.com.
IN TXT "txtvers=1.0" "path=/pub/fedora/linux/extras/$releasever/$basearch"
; Advertise instances for this service
_yum._tcp IN PTR core._yum._tcp
IN PTR updates._yum._tcp
IN PTR extras._yum._tcp
; No other services supported
*._tcp IN SRV 0 0 0 .
*._udp IN SRV 0 0 0 .
(C) Copyright 2006 Red Hat, Inc.
Author: Mihai Ibanescu <misa at redhat.com>
"""
from yum.plugins import TYPE_INTERFACE, TYPE_CORE
from yum.plugins import PluginYumExit
requires_api_version = '2.1'
plugin_type = (TYPE_INTERFACE, TYPE_CORE)
try:
from yum import zeroconf
except ImportError:
zeroconf = None
def init_hook(conduit):
if zeroconf is None:
return
# Debug level - inherit it from the yum's main config file
dl = conduit.getConf().debuglevel
debuglevel = conduit.confInt('main', 'debuglevel', default=dl)
# Strict?
strict = conduit.confBool('main', 'strict', default=False)
# Instantiate (or reset) the service discovery singleton
ServiceDiscovery(debuglevel=debuglevel, strict=strict)
def prereposetup_hook(conduit):
if zeroconf is None:
return
s = ServiceDiscovery()
ret = s.prereposetup_hook(conduit)
s.cleanup()
return ret
class ServiceDiscovery(object):
"""Singleton object to avoid keeping globals around"""
__instance = None
known_protos = [ ('http', 80), ('https', 443), ('ftp', 21), ]
def __new__(typ, *args, **kwargs):
if ServiceDiscovery.__instance is None:
ServiceDiscovery.__instance = object.__new__(typ, *args, **kwargs)
return ServiceDiscovery.__instance
def __init__(self, **kwargs):
"""Constructor - will not change state if called with no arguments"""
if not kwargs:
# No arguments passed, do not change the internal state
return
self.debuglevel = int(kwargs.get('debuglevel', 1))
self.strict = bool(kwargs.get('strict', True))
self.log = None
def prereposetup_hook(self, conduit):
# Expose the logger to other functions
self.log = conduit.info
repos = conduit.getRepos()
for repo in repos.listEnabled():
self.log(4, "prereposetup_hook: Repo id: %s; baseurl: %s" %
(repo.id, repo.baseurl))
# Discover new URL
discovery_urls = self.discover_repo(repo.id)
if not discovery_urls:
# Unable to discover a better repo
continue
self.log(4, "Discovered URLs: %s" % discovery_urls)
if self.strict:
# We only want this URL to serve files
repo.mirrorlist = None
repo.baseurl = discovery_urls
else:
# Prepend the discovered URL to the list
repo.baseurl[0:0] = discovery_urls
# Make sure all variables are expanded
repo.baseurlSetup()
def cleanup(self):
self.log = None
def discover_repo(self, repo_id):
"""Use dns-sd to discover a local repository
Return a list of URLs to use"""
service = "%s._yum._tcp" % repo_id
z = zeroconf.Zeroconf()
try:
services, txts = z.resolve_service(service, search=1)
except zeroconf.Error, e:
self.log(5, "Error discovering repo: %s" % e)
return []
# Loop through the text entries - need to fetch proto and path
# XXX fixme: this part is a bit shaky right now
# if you have more than one text entry and more than one server, it
# will build a cartesian product
extras = []
for txt in txts:
h = self._txt2hash(txt)
proto = h.get('proto', 'http')
path = h.get('path', '')
extras.append((proto, path))
urls = []
for priority, weight, port, target in services:
for proto, path in extras:
if (proto, port) in self.known_protos:
url = "%s://%s%s" % (proto, target, path)
else:
url = "%s://%s:%s%s" % (proto, target, port, path)
urls.append(url)
return urls
def _txt2hash(self, txt):
"""Converts a (set of) text entries in the form name=value to a hash
keyed on the name"""
ret = {}
for t in txt:
arr = t.split('=', 1)
if len(arr) != 2:
self.log(1, "Invalid entry", txt)
continue
ret[arr[0]] = arr[1]
return ret
def main():
pass
if __name__ == '__main__':
main()
-------------- next part --------------
#!/usr/bin/env python -tt
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 Library General Public License for more details.
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# (C) Copyright 2006 Red Hat, Inc.
# Author: Mihai Ibanescu <misa at redhat.com>
import sys
import adns
class Error(Exception):
pass
class Zeroconf:
def __init__(self):
self._client = adns.init()
def browse_browsable_domains(self):
"""Returns all domains that are browsable based on the search list"""
return self._ptr_lookup('b._dns-sd._udp', search=1)
def browse_domain(self, domain):
qname = "_services._dns-sd._udp." + domain
return self._ptr_lookup(qname, search=0)
def browse_services(self, service, search=1):
return self._ptr_lookup(service, search=search)
def resolve_service(self, service, search=0):
srvces = self._srv_lookup(service, search=search)
txts = self._txt_lookup(service, search=search)
return (srvces, txts)
def _ptr_lookup(self, qname, search=0):
"""PTR lookup"""
qflags = self._build_qflags(search=search)
qtype = adns.rr.PTRraw
return self._query(qname, qtype, qflags)
def _srv_lookup(self, qname, search=0):
"""SRV lookup"""
qflags = self._build_qflags(search=search)
qtype = adns.rr.SRVraw
return self._query(qname, qtype, qflags)
def _txt_lookup(self, qname, search=0):
"""TXT lookup"""
qflags = self._build_qflags(search=search)
qtype = adns.rr.TXT
return self._query(qname, qtype, qflags)
def _query(self, qname, qtype, qflags):
ret = self._client.synchronous(qname, qtype, qflags)
if ret[0] != 0:
raise Error(ret)
return ret[3]
def _build_qflags(self, search=0):
qflags = adns.qflags.quoteok_anshost | adns.qflags.quoteok_query
if search:
qflags |= adns.qflags.search
return qflags
def test():
z = Zeroconf()
doms = z.browse_browsable_domains()
for d in doms:
dd = z.browse_domain(d)
print "Domain: %s" % d
for s in dd:
print " Service: %s" % s
srv = z.browse_services(s)
for i in srv:
rez, txts = z.resolve_service(i)
print " Instance: %s; text: %s" % (i, txts)
for r in rez:
print " ", r
#print z.browse_domain()
def test_direct():
z = Zeroconf()
print z.resolve_service('core._yum._tcp', search=1)
def main():
test_direct()
return test()
if __name__ == '__main__':
sys.exit(main() or 0)
More information about the Yum-devel
mailing list