
from slides import Slide, Bullet, SubBullet, URL, Image, PRE
from twslides import Lecture

class PythonSource:
    def __init__(self, content):
        self.content = content
    def toHTML(self):
        return '<pre class="python">%s</pre>' % (self.content,)

class PythonInterpreter:
    def __init__(self, content):
        self.content = content
    def toHTML(self):
        return '<pre class="python-interpreter"">%s</pre>' % (self.content,)

class Shell:
    def __init__(self, content):
        self.content = content
    def toHTML(self):
        return '<pre class="shell">%s</pre>' % (self.content,)

class Raw:
    def __init__(self, content):
        self.content = content
    def toHTML(self):
        return self.content + '\n'

lecture = Lecture(
    "Transluscent Inter-Process Service Migration",
    Slide("What is migration?",
	  Bullet("Hand-off of application-level duties from one process to another",
              SubBullet(Bullet("Database server moving load to another process"),
                        Bullet("Web server moving connection to user process"))),
          Bullet("Existing state passed from old code to new code",
              SubBullet(Bullet("Add features"),
                        Bullet("Incorporate bugfixes")))),
    Slide("Why do we need migration?",
          Bullet("Buggy software needs to be fixed"),
          Bullet("New features need to be rolled out"),
          Bullet("Convenient for rapid development",
                 SubBullet(Bullet("Faster if restarts are not necessary")))),
    Slide("(Too) Simple solutions",
          Bullet("Disconnect all users"),
          Bullet("Shutdown old server"),
          Bullet("Bring up new server"),
          Bullet("Let users reconnect")),
    Slide("Problems with simple solutions",
          Bullet("Interruption of service",
                 SubBullet(Bullet("Network connections lost"),
                           Bullet("Startup overhead incurred"))),
          Bullet("Unexpected difficulties leave service offline",
                 SubBullet(Bullet("Service unavailable while troubleshooting occurs")))),
    Slide("Upgrading code",
          Bullet("In-Process with reload()",
                 SubBullet(Bullet("Difficult to reverse"),
                           Bullet("Leaves two versions of code side-by-side in one process"),
                           Bullet("Upgrade on a per-module basis"))),
          Bullet("Intra-Process",
                 SubBullet(Bullet("Incremental updates"),
                           SubBullet(Bullet("Cheaper code updates means more frequent code updates"),
                                     Bullet("More frequent code updates means smaller code updates"),
                                     Bullet("Upgrade on a per-user basis"))),
                 SubBullet(Bullet("Testable"),
                           SubBullet(Bullet("State can be migrated from live server to test server"),
                                     Bullet("For testing, user connections can be ignored"),
                                     Bullet("Failed migration test leads to defunct test/staging server, leave production server unchanged"))))),
    Slide("Problems with intra-process migration",
          Bullet("Requires all state to be serializable or otherwise persistent",
                 SubBullet(Bullet("Most data-driven services require this anyway"),
                           Bullet("If user data doesn't persist, the service isn't very useful"),
                           Bullet("But migration can introduce unexpected serialization requirements",
                                  SubBullet(Bullet("Twisted Factories / Twisted Protocols")))))),
    Slide("execl()",
          Bullet("Replace existing process with a new one"),
          Bullet("File descriptors remain open"),
          Bullet("Used by numerous MUDs",
                 SubBullet(Bullet("Players happily slashing up monsters"),
                           Bullet("Administrator enters command"),
                           Bullet("Entire game is paused (players are interrupted)"),
                           Bullet("Player data is saved, world-state is not"),
                           Bullet("Process re-executes"),
                           Bullet("Player data loaded from disk, world-state initialized from defaults"),
                           Bullet("Game-play continues (players complain)")))),
    Slide("Problems with execl()",
          Bullet("Monolithic"),
          Bullet("Difficult to test"),
          Bullet("Irreversible"),
          Bullet("Failure can lead to long periods of downtime")),
    Slide("Migration between processes on one machine",
          Bullet("Bring up new server"),
          Bullet("Serialize and transfer state from old server to new server"),
          Bullet("Bring down old server"),
          Bullet("... but doesn't scale up without massively parallel hardware")),
    Slide("Migration between processes on Multiple machines",
          Bullet("Scales way, way up"),
          Bullet("Additional protection from hardware failure"),
          Bullet("More benefits such as useful load balancing")),
    Slide("Problems with multiple machines",
          Bullet("Socket tricks don't work"),
          Bullet("Client disconnect is almost always required"),
          Bullet("Front-end machines can mask over back-end switching",
                 SubBullet(Bullet("More complicated but still feasible"),
                           Bullet("Similar problems as with hardware upgrades")))),
    Slide("Example of simple (stateless) migration",
          Bullet("Start a web server in one process"),
          Bullet("Start a new process"),
          Bullet("Server port passes from old to new"),
          Bullet("Old process exits")),
    Slide("Example of complex (stateful) migration",
          Bullet("Start a Python interpreter accessible via telnet"),
          Bullet("Connect and populate a namespace"),
          Bullet("Start a new process"),
          Bullet("Client connection and server port pass from old to new"),
          Bullet("Old process exits"),
          Bullet("Client connection and namespace in-tact")),
    Slide("Migration - a demonstration - part 1",
          Bullet(PythonInterpreter("""\
>>> import sys, os
>>> sys.version
'2.2.3+ (#1, Feb 25 2004, 23:29:31) \n[GCC 3.3.3 (Debian)]'
>>> os.getpid()
29713
"""))),
    Slide("Migration - a demonstration - part 2",
          Bullet(PythonInterpreter("""\
>>> import sys, os
>>> sys.version
'2.2.3+ (#1, Feb 25 2004, 23:29:31) \n[GCC 3.3.3 (Debian)]'
>>> os.getpid()
29713
""")),
          Bullet("Magic occurs")),
    Slide("Migration - a demonstration - part 3",
          Bullet(PythonInterpreter("""\
>>> import sys, os
>>> sys.version
'2.2.3+ (#1, Feb 25 2004, 23:29:31) \n[GCC 3.3.3 (Debian)]'
>>> os.getpid()
29713""")),
          Bullet("Magic occurs"),
          Bullet(PythonInterpreter("""\
>>> sys.version
'2.3.3 (#2, Jan 13 2004, 00:47:05) \n[GCC 3.3.3 20040110 (prerelease)
(Debian)]'
>>> os.getpid()
29717
"""))),
    Slide("Migration walk-through - part 1",
          Bullet("Start a Twisted server running a telnet factory",
              SubBullet(Bullet(Shell("""\
Log opened.
twistd 1.2.1alpha1 (/usr/bin/python2.3 2.3.3) starting up
reactor class: twisted.internet.default.SelectReactor
Loading test_conn.py...
telnet.ShellFactory starting on 8000
Starting factory &lt;telnet.ShellFactory instance at 0x405acf2c>
Loaded.
twisted.spread.pb.PBServerFactory starting on "'migrate'"
Starting factory &lt;twisted.spread.pb.PBServerFactory instance at 0x405ccb4c>
set uid/gid 1000/1000
"""))))),
    Slide("Migration walk-through - part 2",
          Bullet("Connect to telnet server with telnet client",
                 SubBullet(Bullet(Shell("""\
$ telnet localhost 8000
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

telnet.ShellFactory
Twisted 1.2.1alpha1
username: admin
password: *****
>>> import sys, os
>>> sys.version
'2.2.3+ (#1, Feb 25 2004, 23:29:31) \n[GCC 3.3.3 (Debian)]'
>>> os.getpid()
29713
"""))))),
    Slide("Migration walk-through - part 3",
          Bullet("Start a migration client (server output)",
                 SubBullet(Bullet(Shell("""\
server.ServerFactory starting on "'/tmp/tmp4mUJ49'"
Starting factory &lt;server.ServerFactory instance at 0x405a4d8c>
Jellying &lt;Shell #0 on 8000>
Jellying &lt;telnet.ShellFactory on 8000>
""")),
                           Bullet("Jelly is similar to pickle")))),
    Slide("Migration walk-through - part 4",
          Bullet("Start a migration client (client output)",
                 SubBullet(Bullet(Shell("""\
Starting factory &lt;twisted.spread.pb.PBClientFactory instance at 0x4054090c>
Starting factory &lt;client.ClientFactory instance at 0x4059ce4c>
Owned server &lt;twisted.manhole.telnet.Shell instance at 0x405a0e4c>
Stopping factory &lt;twisted.spread.pb.PBClientFactory instance at 0x4054090c>
Stopping factory &lt;client.ClientFactory instance at 0x4059ce4c>
"""))))),
    Slide("Migration walk-through - part 5",
          Bullet("Server shuts down",
                 SubBullet(Bullet(Shell("""\
(Port 'migrate' Closed)
Stopping factory &lt;twisted.spread.pb.PBServerFactory instance at 0x405ccb4c>
(Port '/tmp/tmp4mUJ49' Closed)
Stopping factory &lt;server.ServerFactory instance at 0x405a4d8c>
Main loop terminated.
Server Shut Down.
"""))))),
    Slide("Migration walk-through - part 6",
          Bullet("What is the client experience?",
                 SubBullet(Bullet(Shell("""\
>>> print sys
&lt;module 'sys' (built-in)>
>>> print os
&lt;module 'os' from '/usr/lib/python2.3/os.pyc'>
"""))))),
    Slide("Integration with existing software",
          Bullet("Serialization code should be kept separate from application code"),
          Bullet("Few changes to application code are required",
                 SubBullet(Bullet("Some are unavoidable"),
                           Bullet(PythonSource("""\
class ShellFactory(telnet.ShellFactory):
    def __init__(self, *a, **kw):
        telnet.ShellFactory.__init__(self, *a, **kw)
        self.protos = {}

    def buildProtocol(self, addr):
        p = telnet.ShellFactory.buildProtocol(self, addr)
        self.protos[addr] = p
        return p
"""))))),
    Slide("Design for migratibility",
          Bullet("Maintain clear separation between code and data"),
          Bullet("Maintain clear separation between different services")),
    Slide("Conclusions",
          Bullet("Multitude of migration options available for Python software"),
          Bullet("Migration without service interruption provides great flexibility for implementors")),
    Slide("Future directions",
          Bullet("Increase flexibility of existing migration code",
                 SubBullet(Bullet("Support more special objects (eg, scheduled events)"),
                           Bullet("Port to new version of PB, when available"))),
          Bullet("UI for managing migrations",
                 SubBullet(Bullet("Render an object graph"),
                           Bullet("Allow user to select objects to migrate"),
                           Bullet("Allow user to select references to break"))),
          Bullet("Load balancing",
                 SubBullet(Bullet("Migrate services between different machines")))),
    Slide("Questions?"))

lecture.renderHTML(".", "migration-%02d.xhtml", css="stylesheet.css")
