Designing a RESTful api for YaST
As we move YaST to the Web, we also want to give it a web-like architecture offering its functionality for widespread consumption.
So we started off by separating the client frontend (user interface) from the server backend (functionality) and settling for a RESTful api between them. We also decided on using Ruby on Rails (usually just named 'Rails') to implement both layers. Besides its widespread adoption outside of OpenSUSE, Rails is used in a couple of company projects like
- SUSE Studio
- OpenSUSE buildservice (frontend)
- OpenFATE
ActiveResource ...
ActiveResource gives you an object-oriented interface for resources, hiding all the HTTP protocol or serialization issues from you.
Following the model-view-controller principle of Rails, resources are separated into their model, behaviour, and visualization. The resource model is expressed as a ruby class below app/models, the resource behaviour and basic lifecycle operations (create-read-update-delete) is implemented in a Controller class living below app/controllers, and app/views hosts the visualization files.
A device resource would be modelled as app/models/device.rb, implemented as app/controllers/devices_controller.rb, and viewed through app/views/devices. (Note that controllers are always plural, even for singleton resources.)
... and routing
To make resource accessible from the outside, routing information must be placed inside config/routes.rb. Routing assigns a URL to the resource, making it accessible from the outside and uniquely identifying it.
For the device resource, a map.resources :device call inside config/routes.rb would publish the resource as /devices, effectively offering a set of URLs and HTTP methods:
| HTTP verb | URL | action | used for |
|---|---|---|---|
| GET | /devices | index | display a list of all devices |
| POST | /devices | create | create a new device |
| GET | /devices/1 | show | display a specific device |
| PUT | /devices/1 | update | update a specific device |
| DELETE | /devices/1 | destroy | delete a specific device |
Limitations of the routing scheme
This scheme of identifying resources via URLs works well for a limited number of resources which all have models below app/models and controllers inside app/controllers.
But it is hard to follow if you allow resources as plugins, which you cannot control and prevent them stomping on another plugins feet. Imagine a printer plugin offering devices and a network plugin doing the same. The /devices URL cannot uniquely identify the resource, a simple
map.resources :devicesrouting is not sufficient.
Pluggable resources
To make pluggable resources work, you have to centralize the routing and restrain from defining URLs. This also matches well with the RESTful principle of hypertext driven APIs and narrowing the URL exposure window.
A resource plugin for the rest-service of YaST is identified by the interface it offers and the controller implementing it.
This is done with a config file in yaml format. It specifies the interface and controller as
interface: org.opensuse.yast.printer.devices
controller: yast/printer/devices
in a .yml file below config/resources. A resource
plugin can offer any number of resources this way.
Interfaces are qualified names like org.opensuse.yast.system.language or org.opensuse.yast.system.users. Ideally, these names should match the prefix for the PolicyKit action controlling access to the resource.
Implementation details
To minimize name clashes, the controller implementing the resource must be inside a namespace. Rails supports this by allowing subdirectories below app/controllers and automatically wrapping controller classes within modules matching the directory names.
Thus a network/device_controller and a printer/device_controller can live side-by-side an be accessed as Network::DeviceController and Printer::DeviceController. You could even nest more deeply like Org::Opensuse::Yast::System::Users, but let's not overdo it.
Rails resource routing puts another restriction on URLs: Rails manages routes as a set of maps, describing URLs as a combination of controller (implementing the resource) and action. Thus the file name and path of the controller define the URL, you cannot name them independently. So the controller: value in the resource description directly affects the resulting URL. Different resource plugins should use different namespaces to effectively prevent name clashes.
Querying the rest-service
The rest-service only publishes a single url, namely /resources, where clients can query for the URL offering a specific interface.
This URL follows RESTful principles since a HTTP GET request returns a list of all resources. Its also possible to request a specific output format, either by setting the Accept header of the http request or by appending .xml or .html.
You can easily try it out by starting the rest-service and pointing a browser to http://localhost:3000/resources.
To find the right resource URL, the client just needs to pass the interface name as ?interface=name and extract the reference from the result. Stay tuned for a future post exploring the client side in more detail.
MonoDevelop on Windows

What you see above is MonoDevelop running on Windows Vista using Microsoft.NET. A lot of effort has gone into making it easy to build MonoDevelop. Mike Kestner has been working on an installer that provides the core libraries on which MD depends on (such as Mono.Addins), and I hope it will be soon available so that people can start using it. We've also fixed the MD project files, so now MD can be built by just opening the main solution in Visual Studio and clicking on build.
A new feature I had to add to MD in order to properly support Windows, is support for multiple runtimes. Thanks to that feature it is possible to select in the IDE which runtime you want to use for building and running a solution. It can be done using a combo box in the toolbar:

There is also a "Run With" menu which shows all runtimes, so you can run a specific project using a specific runtime. Notice that support for multiple runtimes is not specific to Windows, it is also supported in Linux. In this case, you can register several mono runtimes versions installed in different prefixes (I'll blog with more detail about that soon).
The basic funcitonality already works in Windows: loading a project, building running. Other features still don't work, such as the gtk# designer, the nunit add-in or the Subversion add-in. I haven't yet tried none of the add-ins in 'extras'.
I hope we'll be able to make a 2.2 release with a very decent Windows support. I'll keep posting and twittering updates.
openSUSE-GNOME BugDay!
Roll up those sleeves and mark your calendars, because here comes another BugDay!
During Community Week (http://en.opensuse.org/CommunityWeek), I’ll be hosting another openSUSE-GNOME BugDay on Fri, 15 MAY 2009. We’ll start promptly at 1000 CDT and will continue until 1600 CDT. I will be around very early in the day to start prep for the meeting should you have any questions.
We’ll conduct business in #opensuse-gnome on Freenode (irc.freenode.net). I will establish a Gobby session as I’ve done in the past, and we’ll work off of that.
Can’t wait to see you there!
Secret AutoYaST feature :)
So - what does keep_install_network parameter, set to 'true' and inserted into <networking> section of AutoYaST profile, do? One can intuitively guess that it will preserve network configuration - that is, interfaces setup, resolv.conf bits, static routing, udev rules & co. - from 1st stage of installation (of course, only if the installation actually runs over the network) and it is indeed like that. Uwe blogs about it in more details
But unless you use openSUSE Factory, please don't try this at home :)
<networking> <keep_install_network config:type="boolean">true</keep_install_network> </networking>Network interfaces setup (ifcfg files) from the installation will be successfully preserved, but due to a bug, your static routing configuration (if you have any) will be moved into backup file and YaST won't create a new one and you will lose most of the information in /etc/resolv.conf. Unfortunately, nobody got the idea to test with the profile containing nothing more but keep_install_network entry in networking section so far :(
The bug is fixed now for openSUSE 11.2 and SLE11 SP1 (if nothing in particular sub-section of networking section is defined, the setup from installation is used, but AutoYaST profile is the higher authority here). However, if you use SLE11, you'd better install with profile containing at least minimal DNS and routing info, for example like this (set search domain, 1 nameserver and default gateway):
<networking>
<keep_install_network config:type="boolean">true</keep_install_network>
<dns>
<dhcp_hostname config:type="boolean">false</dhcp_hostname>
<domain>home.net</domain>
<hostname>dolphin</hostname>
<nameservers config:type="list">
<nameserver>192.168.0.2</nameserver>
</nameservers>
<searchlist config:type="list">
<search>home.net</search>
</searchlist>
</dns>
<routing>
<ip_forward config:type="boolean">false</ip_forward>
<routes config:type="list">
<route>
<destination>default</destination>
<device>-</device>
<gateway>192.168.0.1</gateway>
<netmask>-</netmask>
</route>
</routes>
</routing>
</networking>Or use a workaround post-script Uwe posted to bugzilla, to backup your setup and to restore it later. One good thing about these bugs is that not only it broadens your knowledge (when user comes up with scenario you never thought of) ;-) but it also helps us to improve things. keep_install_network feature is now documented and not secret anymore. Enjoy it!
GSoC introduction – openSUSE@ARM
Hi openSUSE community!
I’m glad my proposal was accepted and today I want to introduce myself and my GSoC project.
/me , thats Jan-Simon Möller and I’m just finishing my Diploma in electrical engineering at the Leibniz Universität Hannover. I’m coordinator of the openSUSE Weekly Newsletter and contribute also to the hamradio repository, the iFolder project and the openSUSE Build Service. See also my “People of openSUSE” interview.
My Project in short: openSUSE@ARM
My aim during GSoC 2009 is to port first the base to the ARM platform. Then KIWI needs also some attention when it comes to imaging and after that the tools, Kernel and X11.
I’ll heavily use the capabilities of the openSUSE Build Service, which is now ready for ARM.
During the last few days, I’ve done many little preparations to get it all flying when GSoC coding period starts.
Stay tuned !
Suppressing KeyboardInterrupt traceback in Python
If you have a running program in Python and press Ctrl+C, you’ll get a traceback like this:
$ scout java foo
^CTraceback (most recent call last):
File "/usr/bin/scout", line 11, in
ret = scout.ScoutCore.run()
File "/usr/lib64/python2.6/site-packages/scout/__init__.py", line 945, in run
result = module.ScoutModule().main(clp.module_args)
File "/usr/lib64/python2.6/site-packages/scout/__init__.py", line 873, in main
return self.do_query(args.query, repos, args.inversesearch)
File "/usr/lib64/python2.6/site-packages/scout/__init__.py", line 890, in do_query
result.add_rows(self._query(repo, query, inversesearch))
File "/usr/lib64/python2.6/site-packages/scout/__init__.py", line 896, in _query
r = db.query(self._sql, '%%%s%%' % term)
File "/usr/lib64/python2.6/site-packages/scout/__init__.py", line 485, in query
if len(row) == 1: #(2)
KeyboardInterrupt
It is useful suppress it, because user knows he breaks the program and this output should be considered as a bug. Possible solution is wrap a main function by one big try: except KeyboardInterrupt:
try: main() # the main function except KeyboardInterrupt: pass # KeyboardInterrupt supressed
But it makes a new level of indentation which should be uncomfortable – especially in Python. Or when you have multiple entry-points, or just don’t well structured program (which is common when you write your private helper script :)), you maybe prefer another solution.
Python has a sys.excepthook, which is called for traceback printing, so we could define our own and suppress unnecessary output here. And it would be nice suppress only one exception and handle other ones using existing function. And this function make it:
def suppress_keyboard_interrupt_message():
old_excepthook = sys.excepthook
def new_hook(type, value, traceback):
if type != exceptions.KeyboardInterrupt:
old_excepthook(type, value, traceback)
else:
pass
sys.excepthook = new_hook
Function suppress_keyboard_interrupt_message (it is really nice name, don’t it ;-)) stores an existing hook and register an inner function new_hook as a new one. Advantage is that old_excepthook exists only in a scope of this function, so you don’t need use global variables for it.
Update: typos fixed
Ruby style method injection in Python
Ruby has a nice, but very dangerous feature called open classes. That means you can extend any class definition if you want it.
This example shows it
#!/usr/bin/ruby
class Foo
def do_foo
return "foo"
end
end
aFoo = Foo.new()
puts aFoo.do_foo()
class Foo
def do_bar
return "bar"
end
end
puts aFoo.do_bar()
That means you dynamically extend the definition of Foo class, so the last line prints “bar”.
Python behavior is different, because it does not allows this extending. Python just redefines a class Foo, so new instance of Foo will have a do_bar method only. But this not affected an existing ones, like Ruby does.
class Foo:
def do_foo(self):
return "foo"
aFoo1 = Foo()
class Bar(Foo):
def do_nothing(self):
pass
print issubclass(Bar, Foo)
print isinstance(aFoo1, Foo)
class Foo:
def do_bar(self):
return "bar"
aFoo2 = Foo()
aBar = Bar()
print issubclass(Bar, Foo)
print isinstance(aFoo1, Foo)
print isinstance(aFoo2, Foo)
print dir(aFoo1)
print dir(aFoo2)
print dir(aBar)
But what about method injection? My solution is based on ideas of Recipe 81732: Dynamically added methods to a class.
import types
def funcToMethod(func, instance, method_name=None):
cls = instance.__class__ if type(instance) != types.TypeType else instance
setattr(cls, \
method_name or func.__name__, \
types.MethodType(func, instance, cls))
And that’s all. The funcToMethod bounds func to instances’s class and allows under method_name (default name is a same as a function one). So lets do some testing.
class Foo(object):
def list(self):
return [meth for meth in dir(self) if meth[:3] == 'do_' and type(getattr(self, meth)) == types.MethodType]
def do_bar(self):
return "bar"
def do_foo(inst):
return "foo"
class Bar(Foo):
def do_nothing(self):
pass
aBar = Bar()
aFoo1 = Foo()
print aFoo1.list()
# calling it with class instead of instance is also possible and is equivalent
#funcToMethod(do_foo, Foo, "do_foo")
funcToMethod(do_foo, aFoo1, "do_foo")
aFoo2 = Foo()
print aFoo1.list()
print aFoo2.list()
print aFoo1.do_foo()
print aFoo2.do_foo()
print aBar.list()
print aBar.do_foo()
When you run the code, you will see that funcToMethod adds a new method to Foo class and this changes both existing and new instances as Ruby does too. And subclassing is not a problem, they are be affected too.
OpenSUSE Nicaragua @ FLISOL 2009
On Saturday April 25th, the Nicaraguan OpenSUSE Community joined forces with the Free Software Community (GUL-Nic) to celebrate the Latin American Free Software Installation Festival, (FLISOL in Spanish).

My wife Sonia and me, plus Agustin Chavarria and another SuSE fan!
The Festival took place at the Universidad Nacional de Ingenieria Campus (UNI-IES), reaching more than 300 visitors.

OpenSUSE and Novell banners!
Although there were several LUGs performing, OpenSUSE was one of the most solicited distros for installing. We installed the 11.1 version on 7 laptops. And as usual, all others distros were after our stuffed penguins and geekos!

My wife Sonia. Yeah, she loves the Penguin too!

Agustin Chavarria performing the First Instalation of the event: OpenSUSE 11.1!

Fatima Mendoza and Hellman Taleno

A glimpse of the crowd in the Show Room. More and more coming during the day!
We continue working spreading the word of the Green Lizards!
*Update: I corrected the name… shame of me! hehe*
How (not) to install openSUSE 11.1 on ThinkPad T-series
And now repeat after me:
A small intermezzo: though I've been pretending that I'm doing some YaST development for almost 3 years already ;-) I'm not really a technical person. I know a lot about widgets, user interface, usability and all that fancy stuff, but my knowledge of the system configuration ends in /etc/sysconfig or /etc/fstab at most. When it comes to something like system boot, I'm completely lost. At least I admit to it :-) :-D
The installation went on fine - I picked KDE4 as my default desktop environment, had the partitioner shrink Windows partition, created root and /home partition, double-checked whether I got the bootloader installation options right ("Boot from root partition" - check, "Boot from MBR" - uncheck, "Set active flag for boot partition" - check, "Write generic code to MBR" - uncheck) and watched the slideshow. The standard set of 1st stage finish scripts followed and then bang! - "Failed to install the bootloader. Try the configuration again?" (or something along those lines). Hm, if I answer "Yes" now, I'm smart enough to figure out it'll fail again, I thought, unless I change something - but what? Looking at y2log did not make me any less helpless - the last thing I saw was how bootloader uses some cryptic Perl script (/usr/lib/YaST2/bin/tp_mbr, to be exact) to detect ThinkVantage sequence in MBR and preserve it.
Nevermind, I rebooted, picked "Repair installed system" from the installation options and hoped that yast2-repair will help me find out what went wrong. Not surprisingly, it did not. It only showed me already well-known message about failed bootloader installation - quite a cruel joke :) At this point, I gave up. Trying to solve a problem where I don't understand a single bit of its principles is not that much like me. And after all, there are more pleasant ways how to spend Friday night - dancing salsa, for example ... :)
But don't get desperate, my friends - this story has a good ending. I brought my laptop to the wizard :-) I was not looking over his shoulder as he was working, so I don't know what magic spells and potions he used, but I know he did the following:
- Created partitioning setup with separate /boot partition and told the bootloader to boot from it (that was maybe the crucial point)
- Added a repository with openSUSE 11.1 updates as an add-on during installation
- Made extended partition (with swap, /, /home and /boot) 1 cylinder smaller, as ThinkVantage partition at the very end of the disk did not begin exactly at the cylinder boundary
In conclusion, I consider myself to be quite lucky that I'm surrounded by many wizards (bootloader wizard, installation wizard, KDE wizard, some wireless stuff wizards, suspend wizard, ... ) ;-) What do normal openSUSE users who can't grab their machine and bring it to a wizard do, if the same (or similar) thing happens to them? "Oh, they go to #suse and cry there. Or they simply give up and go using Ubuntu" says Darix (whom I sometimes chat with when I suffer from insomnia and he stays at work at night). Sad to say that, but it's probably true ... :-(
