Sat Aug  7 11:03:51 BST 2010  writefaruq@gmail.com
  * multiple-introducer-client-side-001.dpatch
  client.py: Added support to read the multiple introducers config file "basedir/introducers" and to create necessary connections to all introducers.
  web/root.py: Fetches the connection status of all introducer_clients from client
  web/welcome.xhtml: shows the intriducer FURLs and their connection status on WUI's welcome page
  

New patches:

[multiple-introducer-client-side-001.dpatch
writefaruq@gmail.com**20100807100351
 Ignore-this: ad73102b8e1196536782382170314451
 client.py: Added support to read the multiple introducers config file "basedir/introducers" and to create necessary connections to all introducers.
 web/root.py: Fetches the connection status of all introducer_clients from client
 web/welcome.xhtml: shows the intriducer FURLs and their connection status on WUI's welcome page
 
] {
hunk ./src/allmydata/client.py 34
 TiB=1024*GiB
 PiB=1024*TiB
 
+MULTI_INTRODUCERS_CFG = "introducers"
+
 class StubClient(Referenceable):
     implements(RIStubClient)
 
hunk ./src/allmydata/client.py 128
         self.started_timestamp = time.time()
         self.logSource="Client"
         self.DEFAULT_ENCODING_PARAMETERS = self.DEFAULT_ENCODING_PARAMETERS.copy()
-        self.init_introducer_client()
+        self.init_introducer_clients()
         self.init_stats_provider()
         self.init_secrets()
         self.init_storage()
hunk ./src/allmydata/client.py 175
         if os.path.exists(os.path.join(self.basedir, "run_helper")):
             self.set_config("helper", "enabled", "true")
 
-    def init_introducer_client(self):
-        self.introducer_furl = self.get_config("client", "introducer.furl")
-        ic = IntroducerClient(self.tub, self.introducer_furl,
+    def init_introducer_clients(self):              
+        self.introducer_furls = []
+        self.warn_flag = False      
+        # Try to load ""BASEDIR/introducers" cfg file
+        cfg = os.path.join(self.basedir, MULTI_INTRODUCERS_CFG)
+        if os.path.exists(cfg):
+           f = open(cfg, 'r')
+           for introducer_furl in  f.read().split('\n'):
+                if not introducer_furl.strip():
+                    continue
+                self.introducer_furls.append(introducer_furl)
+           f.close()
+        furl_count = len(self.introducer_furls)
+        #print "@icfg: furls: %d" %furl_count
+        
+        # read furl from tahoe.cfg
+        ifurl = self.get_config("client", "introducer.furl", None)
+        if ifurl not in self.introducer_furls: 
+            self.introducer_furls.append(ifurl)
+            f = open(cfg, 'a')
+            f.writelines(ifurl)
+            f.write('\n')
+            f.close()
+            if furl_count > 1:
+                self.warn_flag = True
+                self.log("introducers config file modified.")
+                print "Warning! introducers config file modified."
+
+        # create a pool of introducer_clients
+        self.introducer_clients = [] 
+        for introducer_furl in self.introducer_furls:
+            ic = IntroducerClient(self.tub, introducer_furl,
                               self.nickname,
                               str(allmydata.__full_version__),
                               str(self.OLDEST_SUPPORTED_VERSION))
hunk ./src/allmydata/client.py 210
-        self.introducer_client = ic
+            self.introducer_clients.append(ic)
+        # init introducer_clients as usual      
+        for ic in self.introducer_clients:                    
+            self.init_introducer_client(ic)
+    
+    def init_introducer_client(self, ic):
         # hold off on starting the IntroducerClient until our tub has been
         # started, so we'll have a useful address on our RemoteReference, so
         # that the introducer's status page will show us.
hunk ./src/allmydata/client.py 303
             furl_file = os.path.join(self.basedir, "private", "storage.furl")
             furl = self.tub.registerReference(ss, furlFile=furl_file)
             ri_name = RIStorageServer.__remote_name__
-            self.introducer_client.publish(furl, "storage", ri_name)
+            # Now, publish multiple introducers
+            for ic in self.introducer_clients:
+                ic.publish(furl, "storage", ri_name)           
         d.addCallback(_publish)
         d.addErrback(log.err, facility="tahoe.init",
                      level=log.BAD, umid="aLGBKw")
hunk ./src/allmydata/client.py 359
         # check to see if we're supposed to use the introducer too
         if self.get_config("client-server-selection", "use_introducer",
                            default=True, boolean=True):
-            sb.use_introducer(self.introducer_client)
+            
+            # Now, use our multiple introducers
+            for ic in self.introducer_clients:
+                sb.use_introducer(ic)    
 
     def get_storage_broker(self):
         return self.storage_broker
hunk ./src/allmydata/client.py 375
             sc = StubClient()
             furl = self.tub.registerReference(sc)
             ri_name = RIStubClient.__remote_name__
-            self.introducer_client.publish(furl, "stub_client", ri_name)
+            # Now publish our multiple introducers
+            for ic in self.introducer_clients:
+                ic.publish(furl, "stub_client", ri_name)
         d = self.when_tub_ready()
         d.addCallback(_publish)
         d.addErrback(log.err, facility="tahoe.init",
hunk ./src/allmydata/client.py 489
 
     def get_encoding_parameters(self):
         return self.DEFAULT_ENCODING_PARAMETERS
-
+    
+    # In case we configure multiple introducers
     def connected_to_introducer(self):
hunk ./src/allmydata/client.py 492
-        if self.introducer_client:
-            return self.introducer_client.connected_to_introducer()
-        return False
-
+        status = []
+        if self.introducer_clients:
+            s = False
+            for ic in self.introducer_clients:
+                s = ic.connected_to_introducer()
+                status.append(s)
+        return status
+    
     def get_renewal_secret(self): # this will go away
         return self._secret_holder.get_renewal_secret()
 
hunk ./src/allmydata/web/root.py 220
 
         return ctx.tag[ul]
 
-    def data_introducer_furl(self, ctx, data):
-        return self.client.introducer_furl
-    def data_connected_to_introducer(self, ctx, data):
-        if self.client.connected_to_introducer():
-            return "yes"
-        return "no"
+    # In case we configure multiple introducers
+    def data_introducers(self, ctx, data):
+        connection_status = self.client.connected_to_introducer()          
+        s = []
+        furls = self.client.introducer_furls       
+        for furl in furls:
+            i = furls.index(furl)
+            if connection_status[i]:            
+                s.append( (furl, "Yes") )
+            else:
+                s.append( (furl, "No") )
+        s.sort()
+        return s
+
+    def render_introducers_row(self, ctx, s):
+        (furl, connected) = s
+        #connected = 
+        ctx.fillSlots("introducer_furl", "%s" % (furl))
+        ctx.fillSlots("connected", "%s" % (connected))
+        return ctx.tag
 
     def data_helper_furl(self, ctx, data):
         try:
hunk ./src/allmydata/web/welcome.xhtml 25
     <tr><th>Tahoe-LAFS code imported from:</th> <td n:render="string" n:data="import_path" /></tr>
     <tr><th>Services running:</th> <td n:render="services" /></tr>
   </table>
-  
+
 
 </div>
 
hunk ./src/allmydata/web/welcome.xhtml 40
   <div n:render="download_form" />
 </div>
 
+<h2>Connected Introducer(s)</h2>
+
+<div>
+<table n:render="sequence" n:data="introducers">
+  <tr n:pattern="header">
+    <td>Introducer FURL</td>
+    <td>Connected?</td>
+  </tr>
+  <tr n:pattern="item" n:render="introducers_row">
+    <td><tt><n:slot name="introducer_furl"/></tt></td>
+    <td><tt><n:slot name="connected"/></tt></td>
+  </tr>
+  <tr n:pattern="empty"><td>no introducers!</td></tr>
+</table>
+</div>
+
+
+
 <div class="section" id="grid">
   <h2>Status of the Storage Grid</h2>
 
hunk ./src/allmydata/web/welcome.xhtml 61
-  <div>
-    <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_introducer" /></n:attr>
-    <div>Introducer: <span class="data-chars" n:render="string" n:data="introducer_furl" /></div>
-    <div>Connected to introducer?: <span n:render="string" n:data="connected_to_introducer" /></div>
-  </div>
-
   <div>
     <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_helper" /></n:attr>
     <div>Helper: <span n:render="string" n:data="helper_furl" /></div>
}

Context:

[abbreviate time edge case python2.5 unit test
jacob.lyles@gmail.com**20100729210638
 Ignore-this: 80f9b1dc98ee768372a50be7d0ef66af
] 
[docs: add Jacob Lyles to CREDITS
zooko@zooko.com**20100730230500
 Ignore-this: 9dbbd6a591b4b1a5a8dcb69b7b757792
] 
[web: don't use %d formatting on a potentially large negative float -- there is a bug in Python 2.5 in that case
jacob.lyles@gmail.com**20100730220550
 Ignore-this: 7080eb4bddbcce29cba5447f8f4872ee
 fixes #1055
] 
[test_upload.py: rename test_problem_layout_ticket1124 to test_problem_layout_ticket_1124 -- fix .todo reference.
david-sarah@jacaranda.org**20100729152927
 Ignore-this: c8fe1047edcc83c87b9feb47f4aa587b
] 
[test_upload.py: rename test_problem_layout_ticket1124 to test_problem_layout_ticket_1124 for consistency.
david-sarah@jacaranda.org**20100729142250
 Ignore-this: bc3aad5919ae9079ceb9968ad0f5ea5a
] 
[docs: fix licensing typo that was earlier fixed in [20090921164651-92b7f-7f97b58101d93dc588445c52a9aaa56a2c7ae336]
zooko@zooko.com**20100729052923
 Ignore-this: a975d79115911688e5469d4d869e1664
 I wish we didn't copies of this licensing text in several different files so that changes can be accidentally omitted from some of them.
] 
[misc/build_helpers/run-with-pythonpath.py: fix stale comment, and remove 'trial' example that is not the right way to run trial.
david-sarah@jacaranda.org**20100726225729
 Ignore-this: a61f55557ad69a1633bfb2b8172cce97
] 
[docs/specifications/dirnodes.txt: 'mesh'->'grid'.
david-sarah@jacaranda.org**20100723061616
 Ignore-this: 887bcf921ef00afba8e05e9239035bca
] 
[docs/specifications/dirnodes.txt: bring layer terminology up-to-date with architecture.txt, and a few other updates (e.g. note that the MAC is no longer verified, and that URIs can be unknown). Also 'Tahoe'->'Tahoe-LAFS'.
david-sarah@jacaranda.org**20100723054703
 Ignore-this: f3b98183e7d0a0f391225b8b93ac6c37
] 
[docs: use current cap to Zooko's wiki page in example text
zooko@zooko.com**20100721010543
 Ignore-this: 4f36f36758f9fdbaf9eb73eac23b6652
 fixes #1134
] 
[__init__.py: silence DeprecationWarning about BaseException.message globally. fixes #1129
david-sarah@jacaranda.org**20100720011939
 Ignore-this: 38808986ba79cb2786b010504a22f89
] 
[test_runner: test that 'tahoe --version' outputs no noise (e.g. DeprecationWarnings).
david-sarah@jacaranda.org**20100720011345
 Ignore-this: dd358b7b2e5d57282cbe133e8069702e
] 
[TAG allmydata-tahoe-1.7.1
zooko@zooko.com**20100719131352
 Ignore-this: 6942056548433dc653a746703819ad8c
] 
Patch bundle hash:
a333c46f469094bcbc76587a19d99d7a82c15532