Re: [Freeipa-devel] Command instantiation

2013-01-15 Thread Petr Viktorin

On 01/15/2013 02:39 PM, Simo Sorce wrote:

On Tue, 2013-01-15 at 10:23 +0100, Petr Viktorin wrote:

On 01/14/2013 08:45 PM, John Dennis wrote:

On 01/14/2013 12:56 PM, Jan Cholasta wrote:

On 14.1.2013 18:50, Petr Viktorin wrote:

Ah, yes, you've discovered my ultimate goal: rewriting the whole
framefork :)


It would seem we share the same ultimate goal, sir! :-)


Well it's reassuring I'm not alone in my frustration with elements of
the framework. I thought it was just me :-)

I have one other general complaint about the framework:

Too much magic!

What do I mean by magic? Things which spring into existence at run time
for which there is no static definition. I've spent (wasted) significant
amounts of time trying to figure out how something gets instantiated and
initialized. These things don't exist in the static code, you can't
search for them because they are synthetic. You can see these things
being referenced but you'll never find a class definition or __init__()
method or assignment to a specific object attribute. It's all very very
clever but at the same time very obscure. If you just use the framework
in a cookie-cutter fashion this has probably never bothered you, but if
you have modify what the framework provides it can be difficult.

But I don't want to carp on the framework too much without giving credit
to Jason first. His mandate was to produce a pluggable framework that
was robust, extensible, and supported easy plugin authorship. Jason was
dedicated, almost maniacal in his attention to detail and best
practices. He also had to design most of this himself (the rest of the
team was heads down on other things at the time). It has mostly stood
the test of time. It's pretty hard to anticipate the pain points, that's
something only experience with system can give you down the road, which
is where we find ourselves now.



+1.
It's easy to criticize in hindsight, and I have great respect for the
framework and its author.
Nevertheless, software grows over time and we need to balance bolting
things on with improving the foundations, so that we don't get stuck in
a maze of workarounds in a few years.



Can someone summarize how big a change this would be ?
I do understand the general discussion, but I have not been involved
deeply enough in the framework code to tell.
Also how much would this conflict with the proposed LDAP change ?


No, the LDAP changes won't affect the framework nor the plugin code.


Do we have a way to slowly change stuff or will it require big
all-or-nothing changes ?


As far as I can tell without trying it, the change wouldn't be too 
disruptive.
Command is now immutable, so to current code it shouldn't matter if 
api.Command.xyz returns a "global" instance or a freshly created one.
There will be some complexity to make the change only for Commands and 
not the other plugin types, but it shouldn't be a big change.
The next step is to not lock the Command class. Again I think the 
biggest issue will be to disable the locking only for Commands and not 
other classes in the hierarchy.


When that's done, we can gradually change individual commands to store 
data in the Command object. I'd do one or two to make sure the approach 
works, after that it would be a series of small, localized, low priority 
patches. The main benefit is that *future* code wouldn't have to resort 
to thread-local storage.


--
Petr³

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Re: [Freeipa-devel] Command instantiation

2013-01-15 Thread Simo Sorce
On Tue, 2013-01-15 at 10:23 +0100, Petr Viktorin wrote:
> On 01/14/2013 08:45 PM, John Dennis wrote:
> > On 01/14/2013 12:56 PM, Jan Cholasta wrote:
> >> On 14.1.2013 18:50, Petr Viktorin wrote:
> >>> Ah, yes, you've discovered my ultimate goal: rewriting the whole
> >>> framefork :)
> >>
> >> It would seem we share the same ultimate goal, sir! :-)
> >
> > Well it's reassuring I'm not alone in my frustration with elements of
> > the framework. I thought it was just me :-)
> >
> > I have one other general complaint about the framework:
> >
> > Too much magic!
> >
> > What do I mean by magic? Things which spring into existence at run time
> > for which there is no static definition. I've spent (wasted) significant
> > amounts of time trying to figure out how something gets instantiated and
> > initialized. These things don't exist in the static code, you can't
> > search for them because they are synthetic. You can see these things
> > being referenced but you'll never find a class definition or __init__()
> > method or assignment to a specific object attribute. It's all very very
> > clever but at the same time very obscure. If you just use the framework
> > in a cookie-cutter fashion this has probably never bothered you, but if
> > you have modify what the framework provides it can be difficult.
> >
> > But I don't want to carp on the framework too much without giving credit
> > to Jason first. His mandate was to produce a pluggable framework that
> > was robust, extensible, and supported easy plugin authorship. Jason was
> > dedicated, almost maniacal in his attention to detail and best
> > practices. He also had to design most of this himself (the rest of the
> > team was heads down on other things at the time). It has mostly stood
> > the test of time. It's pretty hard to anticipate the pain points, that's
> > something only experience with system can give you down the road, which
> > is where we find ourselves now.
> >
> 
> +1.
> It's easy to criticize in hindsight, and I have great respect for the 
> framework and its author.
> Nevertheless, software grows over time and we need to balance bolting 
> things on with improving the foundations, so that we don't get stuck in 
> a maze of workarounds in a few years.


Can someone summarize how big a change this would be ?
I do understand the general discussion, but I have not been involved
deeply enough in the framework code to tell.
Also how much would this conflict with the proposed LDAP change ?

Do we have a way to slowly change stuff or will it require big
all-or-nothing changes ?

Simo.

-- 
Simo Sorce * Red Hat, Inc * New York

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-15 Thread Petr Viktorin

On 01/14/2013 08:45 PM, John Dennis wrote:

On 01/14/2013 12:56 PM, Jan Cholasta wrote:

On 14.1.2013 18:50, Petr Viktorin wrote:

Ah, yes, you've discovered my ultimate goal: rewriting the whole
framefork :)


It would seem we share the same ultimate goal, sir! :-)


Well it's reassuring I'm not alone in my frustration with elements of
the framework. I thought it was just me :-)

I have one other general complaint about the framework:

Too much magic!

What do I mean by magic? Things which spring into existence at run time
for which there is no static definition. I've spent (wasted) significant
amounts of time trying to figure out how something gets instantiated and
initialized. These things don't exist in the static code, you can't
search for them because they are synthetic. You can see these things
being referenced but you'll never find a class definition or __init__()
method or assignment to a specific object attribute. It's all very very
clever but at the same time very obscure. If you just use the framework
in a cookie-cutter fashion this has probably never bothered you, but if
you have modify what the framework provides it can be difficult.

But I don't want to carp on the framework too much without giving credit
to Jason first. His mandate was to produce a pluggable framework that
was robust, extensible, and supported easy plugin authorship. Jason was
dedicated, almost maniacal in his attention to detail and best
practices. He also had to design most of this himself (the rest of the
team was heads down on other things at the time). It has mostly stood
the test of time. It's pretty hard to anticipate the pain points, that's
something only experience with system can give you down the road, which
is where we find ourselves now.



+1.
It's easy to criticize in hindsight, and I have great respect for the 
framework and its author.
Nevertheless, software grows over time and we need to balance bolting 
things on with improving the foundations, so that we don't get stuck in 
a maze of workarounds in a few years.



--
Petr³

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread John Dennis

On 01/14/2013 12:56 PM, Jan Cholasta wrote:

On 14.1.2013 18:50, Petr Viktorin wrote:

Ah, yes, you've discovered my ultimate goal: rewriting the whole
framefork :)


It would seem we share the same ultimate goal, sir! :-)


Well it's reassuring I'm not alone in my frustration with elements of 
the framework. I thought it was just me :-)


I have one other general complaint about the framework:

Too much magic!

What do I mean by magic? Things which spring into existence at run time 
for which there is no static definition. I've spent (wasted) significant 
amounts of time trying to figure out how something gets instantiated and 
initialized. These things don't exist in the static code, you can't 
search for them because they are synthetic. You can see these things 
being referenced but you'll never find a class definition or __init__() 
method or assignment to a specific object attribute. It's all very very 
clever but at the same time very obscure. If you just use the framework 
in a cookie-cutter fashion this has probably never bothered you, but if 
you have modify what the framework provides it can be difficult.


But I don't want to carp on the framework too much without giving credit 
to Jason first. His mandate was to produce a pluggable framework that 
was robust, extensible, and supported easy plugin authorship. Jason was 
dedicated, almost maniacal in his attention to detail and best 
practices. He also had to design most of this himself (the rest of the 
team was heads down on other things at the time). It has mostly stood 
the test of time. It's pretty hard to anticipate the pain points, that's 
something only experience with system can give you down the road, which 
is where we find ourselves now.


--
John Dennis 

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread John Dennis

On 01/14/2013 01:17 PM, Rob Crittenden wrote:

Petr Viktorin wrote:



As for clearing the state, you already can't rely on that: the batch
plugin doesn't do it.



Yes, speaking of bolted on, that defines the batch plugin pretty well.
It should be fairly straightforward to clear the state between
executions though (or at least parts of it, there may be some
batch-specif cthings we'd want to maintain).


I think the problem is there are different groups of data being 
maintained in the context and we don't separate them into their logical 
domains. There is connection information that persists across all 
commands in the batch. Then there is the per command information 
specific to each command in the batch. Each should have it's own context.


But as Petr3 points out the per thread context state is kinda a junk box 
where we throw in a undisciplined manner all the things we can't fit 
into a best practices programming model.


Some of this is the fault of the framework design and the priorities 
that drove it. But not all of this can be laid at the feet of our 
framework. Some of it is due to the inflexibility of the core Python 
modules we use and their poor design. A good example is the fact the 
request URL is unavailable when processing a HTTP response in one of the 
libraries we're using, thats just bad design and because we use that 
library we have to live with it, hence sticking the request URL into the 
thread local context. There are numerous example of things like this, 
most are expedient workarounds to bad code design (some under our 
control, some not).



--
John Dennis 

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Petr Viktorin

On 01/14/2013 07:17 PM, Rob Crittenden wrote:

Petr Viktorin wrote:

On 01/14/2013 05:48 PM, John Dennis wrote:

On 01/14/2013 11:06 AM, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's
clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the
methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command
data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



Just a few thoughts, no answers ... :-)

I agree with you that using thread local context blocks to pass
cooperating data is indicative of a design flaw elsewhere.

See the discussion from a couple of days ago concerning api locking and
making our system robust against foreign plugins. As I understand it
that design prohibits modifying the singleton command objects. Under
your proposal how would we maintain that protection? The fact the
commands are singletons is less of an issue than the fact they lock
themselves when the api is finalized. If the commands instead acted as a
"factory" and produced new instances wouldn't they also have to observe
the same locking rules thus defeating the value of instantiating copies
of the command?


Good point. Actually, making api a factory would eliminate the reason
for locking Commands: the foreign plugin could do whatever it wanted to
its instance of the Command, and all would be well unless it modifies
the class itself (which is about as bad as using setattr).


I still think this would invite people to do even more dangerous things,
like changing the API on-the-fly.


Then let them. We're all adults here.
You can't stop people from writing bad plugins. You can't stop attackers 
from killing your machine if you let them run any code. What's the point?


The current design invites *us* to do things like setattr and 
thread-local state, just to get out of the boundaries we've set for 
ourselves.
And the boundaries we need will grow over time, see the discussion of 
bolted-on things below.


Sharp knives are safer than dull ones.


I think the entire design of the plugin system has at it's heart
non-modifiable singleton command objects which does not carry state.


Yes. I guess I'm just trying to find ways to get out of that trap...


context is your state, right? It is sort of bolted on but it is
per-request and guaranteed not to leak into anything else (except batch,
as noted below).


We have and support the batch plugin, so that guarantee isn't worth much.

Thread-local state works for now but as more things are built on it, 
it'll become unmanageable.



FWIW, I was never convinced the trade-off between protecting our API and
being able to make smart coding choices (such as your suggestion) struck
the right balance.

Going back to one of your suggestions of passing a "context block" as a
parameter. Our method signatures do not currently support that as you
observe. But given the fact by conscious design a thread only executes
one *top-level* command at a time and then clears it state the thread
local context block is effectively playing the almost exactly same role
as if it were passed as a parameter.


Almost. But, lots of Commands call other Commands, and the caller needs
to know the internals of the callee to make sure the context attributes
don't clash. Not to mention the other things, such as connection
backends, that use the thread-local object. It's fragile.


No argument there. We could clean this up somewhat by imposing a
namespace into variable naming.


As for clearing the state, you already can't rely on that: the batch
plugin doesn't do it.



Yes, speaking of bolted on, that defines the batch plugin pretty well.
It should be fairly straightforward to clear the state between
executions though (or at least parts of it, there may be some
batch-specif cthings we'd want to maintain).



There are also conne

Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Rob Crittenden

Petr Viktorin wrote:

On 01/14/2013 05:48 PM, John Dennis wrote:

On 01/14/2013 11:06 AM, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command
data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



Just a few thoughts, no answers ... :-)

I agree with you that using thread local context blocks to pass
cooperating data is indicative of a design flaw elsewhere.

See the discussion from a couple of days ago concerning api locking and
making our system robust against foreign plugins. As I understand it
that design prohibits modifying the singleton command objects. Under
your proposal how would we maintain that protection? The fact the
commands are singletons is less of an issue than the fact they lock
themselves when the api is finalized. If the commands instead acted as a
"factory" and produced new instances wouldn't they also have to observe
the same locking rules thus defeating the value of instantiating copies
of the command?


Good point. Actually, making api a factory would eliminate the reason
for locking Commands: the foreign plugin could do whatever it wanted to
its instance of the Command, and all would be well unless it modifies
the class itself (which is about as bad as using setattr).


I still think this would invite people to do even more dangerous things, 
like changing the API on-the-fly.





I think the entire design of the plugin system has at it's heart
non-modifiable singleton command objects which does not carry state.


Yes. I guess I'm just trying to find ways to get out of that trap...


context is your state, right? It is sort of bolted on but it is 
per-request and guaranteed not to leak into anything else (except batch, 
as noted below).





FWIW, I was never convinced the trade-off between protecting our API and
being able to make smart coding choices (such as your suggestion) struck
the right balance.

Going back to one of your suggestions of passing a "context block" as a
parameter. Our method signatures do not currently support that as you
observe. But given the fact by conscious design a thread only executes
one *top-level* command at a time and then clears it state the thread
local context block is effectively playing the almost exactly same role
as if it were passed as a parameter.


Almost. But, lots of Commands call other Commands, and the caller needs
to know the internals of the callee to make sure the context attributes
don't clash. Not to mention the other things, such as connection
backends, that use the thread-local object. It's fragile.


No argument there. We could clean this up somewhat by imposing a 
namespace into variable naming.




As for clearing the state, you already can't rely on that: the batch
plugin doesn't do it.



Yes, speaking of bolted on, that defines the batch plugin pretty well. 
It should be fairly straightforward to clear the state between 
executions though (or at least parts of it, there may be some 
batch-specif cthings we'd want to maintain).


rob

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Jan Cholasta

On 14.1.2013 18:50, Petr Viktorin wrote:

On 01/14/2013 06:31 PM, Alexander Bokovoy wrote:

On Mon, 14 Jan 2013, Jan Cholasta wrote:

On 14.1.2013 17:06, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's
clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the
methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command
data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



You could extend that to other plugin types as well (e.g. having
Object instances with access to a single object's params instead of
passing data around in a dict would be superb), but I'm afraid this
kind of change won't be easy to do now.



Ah, yes, you've discovered my ultimate goal: rewriting the whole
framefork :)


It would seem we share the same ultimate goal, sir! :-)


But, rewriting the whole framework at once is not realistic, so I'd like
to do this in little steps. Starting with Commands. That should be
possible.


The framework was designed around singleton plugin objects right from
the beginning. I personally think this design sucks, as every kind of
entity in IPA is described by such an object (object classes are
singleton objects, methods are singleton objects, etc.), instead of
using appropriate Python primitives for the job (object classes should
be Python classes, methods should be methods of these classes, etc.).

One note here. Having method classes separated from the object classes
allows to add new methods to existing objects without affecting those
objects and without redefining object classes.

Ability to write simple plugins to extend existing objects is very
attractive.

In case of methods being methods of Python object classes you'd lose this
feature and in order to gain it back you would need to deal with
some sort of method descriptors -- in many cases methods are in fact
collection of actual procedures working together, for example, most
of CRUD methods have pre/post modifier callbacks, spanned over elaborate
Method inheritance tree and allow to amend actual execute code.
Pulling this into a single Python method would require more work that
would later grow into an imitation of a separate method class anyway.



Well, that is your idea, there are more ways around this. It would 
certainly feel more natural to work with such objects in Python than 
what we do now (feels rather C-ish to me).




The difference between functions, methods, and callable objects in
Python is surprisingly small. I'd bet allowing the implementer to use
the best tool out out of those for her job would even involve less magic
than the current approach. (We actually parse the class names with a
regex!)


+1




But as I said, this is generalizing my idea too much. I'd like to keep
my feet on the ground so I'm currently proposing the change for Commands
only.




--
Jan Cholasta

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Petr Viktorin

On 01/14/2013 06:31 PM, Alexander Bokovoy wrote:

On Mon, 14 Jan 2013, Jan Cholasta wrote:

On 14.1.2013 17:06, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command
data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



You could extend that to other plugin types as well (e.g. having
Object instances with access to a single object's params instead of
passing data around in a dict would be superb), but I'm afraid this
kind of change won't be easy to do now.



Ah, yes, you've discovered my ultimate goal: rewriting the whole 
framefork :)
But, rewriting the whole framework at once is not realistic, so I'd like 
to do this in little steps. Starting with Commands. That should be possible.



The framework was designed around singleton plugin objects right from
the beginning. I personally think this design sucks, as every kind of
entity in IPA is described by such an object (object classes are
singleton objects, methods are singleton objects, etc.), instead of
using appropriate Python primitives for the job (object classes should
be Python classes, methods should be methods of these classes, etc.).

One note here. Having method classes separated from the object classes
allows to add new methods to existing objects without affecting those
objects and without redefining object classes.

Ability to write simple plugins to extend existing objects is very
attractive.

In case of methods being methods of Python object classes you'd lose this
feature and in order to gain it back you would need to deal with
some sort of method descriptors -- in many cases methods are in fact
collection of actual procedures working together, for example, most
of CRUD methods have pre/post modifier callbacks, spanned over elaborate
Method inheritance tree and allow to amend actual execute code.
Pulling this into a single Python method would require more work that
would later grow into an imitation of a separate method class anyway.



The difference between functions, methods, and callable objects in 
Python is surprisingly small. I'd bet allowing the implementer to use 
the best tool out out of those for her job would even involve less magic 
than the current approach. (We actually parse the class names with a regex!)



But as I said, this is generalizing my idea too much. I'd like to keep 
my feet on the ground so I'm currently proposing the change for Commands 
only.


--
Petr³

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Alexander Bokovoy

On Mon, 14 Jan 2013, Jan Cholasta wrote:

On 14.1.2013 17:06, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



You could extend that to other plugin types as well (e.g. having 
Object instances with access to a single object's params instead of 
passing data around in a dict would be superb), but I'm afraid this 
kind of change won't be easy to do now.


The framework was designed around singleton plugin objects right from 
the beginning. I personally think this design sucks, as every kind of 
entity in IPA is described by such an object (object classes are 
singleton objects, methods are singleton objects, etc.), instead of 
using appropriate Python primitives for the job (object classes 
should be Python classes, methods should be methods of these classes, 
etc.).

One note here. Having method classes separated from the object classes
allows to add new methods to existing objects without affecting those
objects and without redefining object classes.

Ability to write simple plugins to extend existing objects is very
attractive.

In case of methods being methods of Python object classes you'd lose this
feature and in order to gain it back you would need to deal with
some sort of method descriptors -- in many cases methods are in fact
collection of actual procedures working together, for example, most
of CRUD methods have pre/post modifier callbacks, spanned over elaborate
Method inheritance tree and allow to amend actual execute code. 


Pulling this into a single Python method would require more work that
would later grow into an imitation of a separate method class anyway.

--
/ Alexander Bokovoy

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Petr Viktorin

On 01/14/2013 05:48 PM, John Dennis wrote:

On 01/14/2013 11:06 AM, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command
data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



Just a few thoughts, no answers ... :-)

I agree with you that using thread local context blocks to pass
cooperating data is indicative of a design flaw elsewhere.

See the discussion from a couple of days ago concerning api locking and
making our system robust against foreign plugins. As I understand it
that design prohibits modifying the singleton command objects. Under
your proposal how would we maintain that protection? The fact the
commands are singletons is less of an issue than the fact they lock
themselves when the api is finalized. If the commands instead acted as a
"factory" and produced new instances wouldn't they also have to observe
the same locking rules thus defeating the value of instantiating copies
of the command?


Good point. Actually, making api a factory would eliminate the reason 
for locking Commands: the foreign plugin could do whatever it wanted to 
its instance of the Command, and all would be well unless it modifies 
the class itself (which is about as bad as using setattr).



I think the entire design of the plugin system has at it's heart
non-modifiable singleton command objects which does not carry state.


Yes. I guess I'm just trying to find ways to get out of that trap...


FWIW, I was never convinced the trade-off between protecting our API and
being able to make smart coding choices (such as your suggestion) struck
the right balance.

Going back to one of your suggestions of passing a "context block" as a
parameter. Our method signatures do not currently support that as you
observe. But given the fact by conscious design a thread only executes
one *top-level* command at a time and then clears it state the thread
local context block is effectively playing the almost exactly same role
as if it were passed as a parameter.


Almost. But, lots of Commands call other Commands, and the caller needs 
to know the internals of the callee to make sure the context attributes 
don't clash. Not to mention the other things, such as connection 
backends, that use the thread-local object. It's fragile.


As for clearing the state, you already can't rely on that: the batch 
plugin doesn't do it.


--
Petr³

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread Jan Cholasta

On 14.1.2013 17:06, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



You could extend that to other plugin types as well (e.g. having Object 
instances with access to a single object's params instead of passing 
data around in a dict would be superb), but I'm afraid this kind of 
change won't be easy to do now.


The framework was designed around singleton plugin objects right from 
the beginning. I personally think this design sucks, as every kind of 
entity in IPA is described by such an object (object classes are 
singleton objects, methods are singleton objects, etc.), instead of 
using appropriate Python primitives for the job (object classes should 
be Python classes, methods should be methods of these classes, etc.).


I really would like to see this improve, but I'm not sure if it's 
possible without rewriting the whole framework.


Honza

--
Jan Cholasta

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] Command instantiation

2013-01-14 Thread John Dennis

On 01/14/2013 11:06 AM, Petr Viktorin wrote:


IPA Command objects sometimes need to pass some data between their
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in
pre_callback and then checks it in execute.

While that works for now, it's far from best practice. For example, if
some Command can call another Command, we need to carefully check that
the global data isn't overwritten.


The other way data is passed around is arguments. The callback methods
take a bunch of arguments that are the same for a particular Command
invocation (ldap, entry_attrs, *keys, **options). By now, there's no
hope of adding a new one, since all the callbacks would need to be
rewritten. (Well, one could add an artificial option, but that's clearly
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object
and passing that around. Or better, putting it in the object the methods
are called on.

This got me thinking -- why do we not make a new Command instance for
every command invocation? Currently Command objects are only created
once and added to the api, so they can't be used to store per-command data.
It seems that having `api.Command.user_add` create a new instance would
work better for us. (Of course it's not the best syntax for creating a
new object, but having to change all the calling code would be too
disruptive).
What do you think?



Just a few thoughts, no answers ... :-)

I agree with you that using thread local context blocks to pass 
cooperating data is indicative of a design flaw elsewhere.


See the discussion from a couple of days ago concerning api locking and 
making our system robust against foreign plugins. As I understand it 
that design prohibits modifying the singleton command objects. Under 
your proposal how would we maintain that protection? The fact the 
commands are singletons is less of an issue than the fact they lock 
themselves when the api is finalized. If the commands instead acted as a 
"factory" and produced new instances wouldn't they also have to observe 
the same locking rules thus defeating the value of instantiating copies 
of the command?


I think the entire design of the plugin system has at it's heart 
non-modifiable singleton command objects which does not carry state.


FWIW, I was never convinced the trade-off between protecting our API and 
being able to make smart coding choices (such as your suggestion) struck 
the right balance.


Going back to one of your suggestions of passing a "context block" as a 
parameter. Our method signatures do not currently support that as you 
observe. But given the fact by conscious design a thread only executes 
one *top-level* command at a time and then clears it state the thread 
local context block is effectively playing the almost exactly same role 
as if it were passed as a parameter.




--
John Dennis 

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


[Freeipa-devel] Command instantiation

2013-01-14 Thread Petr Viktorin


IPA Command objects sometimes need to pass some data between their 
various methods. Currently that's done using the thread-local context.
For an example see dnsrecord_del, which sets a "del_all" flag in 
pre_callback and then checks it in execute.


While that works for now, it's far from best practice. For example, if 
some Command can call another Command, we need to carefully check that 
the global data isn't overwritten.



The other way data is passed around is arguments. The callback methods 
take a bunch of arguments that are the same for a particular Command 
invocation (ldap, entry_attrs, *keys, **options). By now, there's no 
hope of adding a new one, since all the callbacks would need to be 
rewritten. (Well, one could add an artificial option, but that's clearly 
not a good solution.)
In OOP, this problem is usually solved by putting the data in an object 
and passing that around. Or better, putting it in the object the methods 
are called on.


This got me thinking -- why do we not make a new Command instance for 
every command invocation? Currently Command objects are only created 
once and added to the api, so they can't be used to store per-command data.
It seems that having `api.Command.user_add` create a new instance would 
work better for us. (Of course it's not the best syntax for creating a 
new object, but having to change all the calling code would be too 
disruptive).

What do you think?

--
Petr³

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel