MiniMRCluster.java
上传用户:quxuerui
上传日期:2018-01-08
资源大小:41811k
文件大小:20k
源码类别:

网格计算

开发平台:

Java

  1. /**
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *     http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing, software
  13.  * distributed under the License is distributed on an "AS IS" BASIS,
  14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  * See the License for the specific language governing permissions and
  16.  * limitations under the License.
  17.  */
  18. package org.apache.hadoop.mapred;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.util.ArrayList;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import org.apache.commons.logging.Log;
  25. import org.apache.commons.logging.LogFactory;
  26. import org.apache.hadoop.fs.FileSystem;
  27. import org.apache.hadoop.net.DNSToSwitchMapping;
  28. import org.apache.hadoop.net.NetUtils;
  29. import org.apache.hadoop.net.NetworkTopology;
  30. import org.apache.hadoop.net.StaticMapping;
  31. import org.apache.hadoop.security.UnixUserGroupInformation;
  32. /**
  33.  * This class creates a single-process Map-Reduce cluster for junit testing.
  34.  * One thread is created for each server.
  35.  */
  36. public class MiniMRCluster {
  37.   private static final Log LOG = LogFactory.getLog(MiniMRCluster.class);
  38.     
  39.   private Thread jobTrackerThread;
  40.   private JobTrackerRunner jobTracker;
  41.     
  42.   private int jobTrackerPort = 0;
  43.   private int taskTrackerPort = 0;
  44.   private int jobTrackerInfoPort = 0;
  45.   private int numTaskTrackers;
  46.     
  47.   private List<TaskTrackerRunner> taskTrackerList = new ArrayList<TaskTrackerRunner>();
  48.   private List<Thread> taskTrackerThreadList = new ArrayList<Thread>();
  49.     
  50.   private String namenode;
  51.   private UnixUserGroupInformation ugi = null;
  52.   private JobConf conf;
  53.     
  54.   private JobConf job;
  55.   
  56.   /**
  57.    * An inner class that runs a job tracker.
  58.    */
  59.   class JobTrackerRunner implements Runnable {
  60.     private JobTracker tracker = null;
  61.     private volatile boolean isActive = true;
  62.     
  63.     JobConf jc = null;
  64.         
  65.     public JobTrackerRunner(JobConf conf) {
  66.       jc = conf;
  67.     }
  68.     public boolean isUp() {
  69.       return (tracker != null);
  70.     }
  71.         
  72.     public boolean isActive() {
  73.       return isActive;
  74.     }
  75.     public int getJobTrackerPort() {
  76.       return tracker.getTrackerPort();
  77.     }
  78.     public int getJobTrackerInfoPort() {
  79.       return tracker.getInfoPort();
  80.     }
  81.   
  82.     public JobTracker getJobTracker() {
  83.       return tracker;
  84.     }
  85.     
  86.     /**
  87.      * Create the job tracker and run it.
  88.      */
  89.     public void run() {
  90.       try {
  91.         jc = (jc == null) ? createJobConf() : createJobConf(jc);
  92.         jc.set("mapred.local.dir","build/test/mapred/local");
  93.         jc.setClass("topology.node.switch.mapping.impl", 
  94.             StaticMapping.class, DNSToSwitchMapping.class);
  95.         tracker = JobTracker.startTracker(jc);
  96.         tracker.offerService();
  97.       } catch (Throwable e) {
  98.         LOG.error("Job tracker crashed", e);
  99.         isActive = false;
  100.       }
  101.     }
  102.         
  103.     /**
  104.      * Shutdown the job tracker and wait for it to finish.
  105.      */
  106.     public void shutdown() {
  107.       try {
  108.         if (tracker != null) {
  109.           tracker.stopTracker();
  110.         }
  111.       } catch (Throwable e) {
  112.         LOG.error("Problem shutting down job tracker", e);
  113.       }
  114.       isActive = false;
  115.     }
  116.   }
  117.     
  118.   /**
  119.    * An inner class to run the task tracker.
  120.    */
  121.   class TaskTrackerRunner implements Runnable {
  122.     volatile TaskTracker tt;
  123.     int trackerId;
  124.     // the localDirs for this taskTracker
  125.     String[] localDirs;
  126.     volatile boolean isInitialized = false;
  127.     volatile boolean isDead = false;
  128.     int numDir;
  129.     TaskTrackerRunner(int trackerId, int numDir, String hostname, 
  130.                                     JobConf cfg) 
  131.     throws IOException {
  132.       this.trackerId = trackerId;
  133.       this.numDir = numDir;
  134.       localDirs = new String[numDir];
  135.       JobConf conf = null;
  136.       if (cfg == null) {
  137.         conf = createJobConf();
  138.       } else {
  139.         conf = createJobConf(cfg);
  140.       }
  141.       if (hostname != null) {
  142.         conf.set("slave.host.name", hostname);
  143.       }
  144.       conf.set("mapred.task.tracker.http.address", "0.0.0.0:0");
  145.       conf.set("mapred.task.tracker.report.address", 
  146.                 "127.0.0.1:" + taskTrackerPort);
  147.       File localDirBase = 
  148.         new File(conf.get("mapred.local.dir")).getAbsoluteFile();
  149.       localDirBase.mkdirs();
  150.       StringBuffer localPath = new StringBuffer();
  151.       for(int i=0; i < numDir; ++i) {
  152.         File ttDir = new File(localDirBase, 
  153.                               Integer.toString(trackerId) + "_" + 0);
  154.         if (!ttDir.mkdirs()) {
  155.           if (!ttDir.isDirectory()) {
  156.             throw new IOException("Mkdirs failed to create " + ttDir);
  157.           }
  158.         }
  159.         localDirs[i] = ttDir.toString();
  160.         if (i != 0) {
  161.           localPath.append(",");
  162.         }
  163.         localPath.append(localDirs[i]);
  164.       }
  165.       conf.set("mapred.local.dir", localPath.toString());
  166.       LOG.info("mapred.local.dir is " +  localPath);
  167.       try {
  168.         tt = new TaskTracker(conf);
  169.         isInitialized = true;
  170.       } catch (Throwable e) {
  171.         isDead = true;
  172.         tt = null;
  173.         LOG.error("task tracker " + trackerId + " crashed", e);
  174.       }
  175.     }
  176.         
  177.     /**
  178.      * Create and run the task tracker.
  179.      */
  180.     public void run() {
  181.       try {
  182.         if (tt != null) {
  183.           tt.run();
  184.         }
  185.       } catch (Throwable e) {
  186.         isDead = true;
  187.         tt = null;
  188.         LOG.error("task tracker " + trackerId + " crashed", e);
  189.       }
  190.     }
  191.         
  192.     /**
  193.      * Get the local dir for this TaskTracker.
  194.      * This is there so that we do not break
  195.      * previous tests. 
  196.      * @return the absolute pathname
  197.      */
  198.     public String getLocalDir() {
  199.       return localDirs[0];
  200.     }
  201.        
  202.     public String[] getLocalDirs(){
  203.       return localDirs;
  204.     } 
  205.     
  206.     public TaskTracker getTaskTracker() {
  207.       return tt;
  208.     }
  209.     
  210.     /**
  211.      * Shut down the server and wait for it to finish.
  212.      */
  213.     public void shutdown() {
  214.       if (tt != null) {
  215.         try {
  216.           tt.shutdown();
  217.         } catch (Throwable e) {
  218.           LOG.error("task tracker " + trackerId + " could not shut down",
  219.                     e);
  220.         }
  221.       }
  222.     }
  223.   }
  224.     
  225.   /**
  226.    * Get the local directory for the Nth task tracker
  227.    * @param taskTracker the index of the task tracker to check
  228.    * @return the absolute pathname of the local dir
  229.    */
  230.   public String getTaskTrackerLocalDir(int taskTracker) {
  231.     return (taskTrackerList.get(taskTracker)).getLocalDir();
  232.   }
  233.   public JobTrackerRunner getJobTrackerRunner() {
  234.     return jobTracker;
  235.   }
  236.   
  237.   TaskTrackerRunner getTaskTrackerRunner(int id) {
  238.     return taskTrackerList.get(id);
  239.   }
  240.   /**
  241.    * Get the number of task trackers in the cluster
  242.    */
  243.   public int getNumTaskTrackers() {
  244.     return taskTrackerList.size();
  245.   }
  246.     
  247.   /**
  248.    * Wait until the system is idle.
  249.    */
  250.   public void waitUntilIdle() {
  251.     waitTaskTrackers();
  252.     
  253.     JobClient client;
  254.     try {
  255.       client = new JobClient(job);
  256.       while(client.getClusterStatus().getTaskTrackers()<taskTrackerList.size()) {
  257.         for(TaskTrackerRunner runner : taskTrackerList) {
  258.           if(runner.isDead) {
  259.             throw new RuntimeException("TaskTracker is dead");
  260.           }
  261.         }
  262.         Thread.sleep(1000);
  263.       }
  264.     }
  265.     catch (IOException ex) {
  266.       throw new RuntimeException(ex);
  267.     }
  268.     catch (InterruptedException ex) {
  269.       throw new RuntimeException(ex);
  270.     }
  271.     
  272.   }
  273.   private void waitTaskTrackers() {
  274.     for(Iterator<TaskTrackerRunner> itr= taskTrackerList.iterator(); itr.hasNext();) {
  275.       TaskTrackerRunner runner = itr.next();
  276.       while (!runner.isDead && (!runner.isInitialized || !runner.tt.isIdle())) {
  277.         if (!runner.isInitialized) {
  278.           LOG.info("Waiting for task tracker to start.");
  279.         } else {
  280.           LOG.info("Waiting for task tracker " + runner.tt.getName() +
  281.                    " to be idle.");
  282.         }
  283.         try {
  284.           Thread.sleep(1000);
  285.         } catch (InterruptedException ie) {}
  286.       }
  287.     }
  288.   }
  289.   
  290.   /** 
  291.    * Get the actual rpc port used.
  292.    */
  293.   public int getJobTrackerPort() {
  294.     return jobTrackerPort;
  295.   }
  296.   public JobConf createJobConf() {
  297.     return createJobConf(new JobConf());
  298.   }
  299.   public JobConf createJobConf(JobConf conf) {
  300.     if(conf == null) {
  301.       conf = new JobConf();
  302.     }
  303.     JobConf result = new JobConf(conf);
  304.     FileSystem.setDefaultUri(result, namenode);
  305.     result.set("mapred.job.tracker", "localhost:"+jobTrackerPort);
  306.     result.set("mapred.job.tracker.http.address", 
  307.                         "127.0.0.1:" + jobTrackerInfoPort);
  308.     if (ugi != null) {
  309.       result.set("mapred.system.dir", "/mapred/system");
  310.       UnixUserGroupInformation.saveToConf(result,
  311.           UnixUserGroupInformation.UGI_PROPERTY_NAME, ugi);
  312.     }
  313.     // for debugging have all task output sent to the test output
  314.     JobClient.setTaskOutputFilter(result, JobClient.TaskStatusFilter.ALL);
  315.     return result;
  316.   }
  317.   /**
  318.    * Create the config and the cluster.
  319.    * @param numTaskTrackers no. of tasktrackers in the cluster
  320.    * @param namenode the namenode
  321.    * @param numDir no. of directories
  322.    * @throws IOException
  323.    */
  324.   public MiniMRCluster(int numTaskTrackers, String namenode, int numDir, 
  325.       String[] racks, String[] hosts) throws IOException {
  326.     this(0, 0, numTaskTrackers, namenode, numDir, racks, hosts);
  327.   }
  328.   
  329.   /**
  330.    * Create the config and the cluster.
  331.    * @param numTaskTrackers no. of tasktrackers in the cluster
  332.    * @param namenode the namenode
  333.    * @param numDir no. of directories
  334.    * @param racks Array of racks
  335.    * @param hosts Array of hosts in the corresponding racks
  336.    * @param conf Default conf for the jobtracker
  337.    * @throws IOException
  338.    */
  339.   public MiniMRCluster(int numTaskTrackers, String namenode, int numDir, 
  340.                        String[] racks, String[] hosts, JobConf conf) 
  341.   throws IOException {
  342.     this(0, 0, numTaskTrackers, namenode, numDir, racks, hosts, null, conf);
  343.   }
  344.   /**
  345.    * Create the config and the cluster.
  346.    * @param numTaskTrackers no. of tasktrackers in the cluster
  347.    * @param namenode the namenode
  348.    * @param numDir no. of directories
  349.    * @throws IOException
  350.    */
  351.   public MiniMRCluster(int numTaskTrackers, String namenode, int numDir) 
  352.     throws IOException {
  353.     this(0, 0, numTaskTrackers, namenode, numDir);
  354.   }
  355.     
  356.   public MiniMRCluster(int jobTrackerPort,
  357.       int taskTrackerPort,
  358.       int numTaskTrackers,
  359.       String namenode,
  360.       int numDir)
  361.   throws IOException {
  362.     this(jobTrackerPort, taskTrackerPort, numTaskTrackers, namenode, 
  363.          numDir, null);
  364.   }
  365.   
  366.   public MiniMRCluster(int jobTrackerPort,
  367.       int taskTrackerPort,
  368.       int numTaskTrackers,
  369.       String namenode,
  370.       int numDir,
  371.       String[] racks) throws IOException {
  372.     this(jobTrackerPort, taskTrackerPort, numTaskTrackers, namenode, 
  373.          numDir, racks, null);
  374.   }
  375.   
  376.   public MiniMRCluster(int jobTrackerPort,
  377.                        int taskTrackerPort,
  378.                        int numTaskTrackers,
  379.                        String namenode,
  380.                        int numDir,
  381.                        String[] racks, String[] hosts) throws IOException {
  382.     this(jobTrackerPort, taskTrackerPort, numTaskTrackers, namenode, 
  383.          numDir, racks, hosts, null);
  384.   }
  385.   public MiniMRCluster(int jobTrackerPort, int taskTrackerPort,
  386.       int numTaskTrackers, String namenode, 
  387.       int numDir, String[] racks, String[] hosts, UnixUserGroupInformation ugi
  388.       ) throws IOException {
  389.     this(jobTrackerPort, taskTrackerPort, numTaskTrackers, namenode, 
  390.          numDir, racks, hosts, ugi, null);
  391.   }
  392.   public MiniMRCluster(int jobTrackerPort, int taskTrackerPort,
  393.       int numTaskTrackers, String namenode, 
  394.       int numDir, String[] racks, String[] hosts, UnixUserGroupInformation ugi,
  395.       JobConf conf) throws IOException {
  396.     if (racks != null && racks.length < numTaskTrackers) {
  397.       LOG.error("Invalid number of racks specified. It should be at least " +
  398.           "equal to the number of tasktrackers");
  399.       shutdown();
  400.     }
  401.     if (hosts != null && numTaskTrackers > hosts.length ) {
  402.       throw new IllegalArgumentException( "The length of hosts [" + hosts.length
  403.           + "] is less than the number of tasktrackers [" + numTaskTrackers + "].");
  404.     }
  405.      
  406.      //Generate rack names if required
  407.      if (racks == null) {
  408.        System.out.println("Generating rack names for tasktrackers");
  409.        racks = new String[numTaskTrackers];
  410.        for (int i=0; i < racks.length; ++i) {
  411.          racks[i] = NetworkTopology.DEFAULT_RACK;
  412.        }
  413.      }
  414.      
  415.     //Generate some hostnames if required
  416.     if (hosts == null) {
  417.       System.out.println("Generating host names for tasktrackers");
  418.       hosts = new String[numTaskTrackers];
  419.       for (int i = 0; i < numTaskTrackers; i++) {
  420.         hosts[i] = "host" + i + ".foo.com";
  421.       }
  422.     }
  423.     this.jobTrackerPort = jobTrackerPort;
  424.     this.taskTrackerPort = taskTrackerPort;
  425.     this.jobTrackerInfoPort = 0;
  426.     this.numTaskTrackers = 0;
  427.     this.namenode = namenode;
  428.     this.ugi = ugi;
  429.     this.conf = conf; // this is the conf the mr starts with
  430.     // start the jobtracker
  431.     startJobTracker();
  432.     // Create the TaskTrackers
  433.     for (int idx = 0; idx < numTaskTrackers; idx++) {
  434.       String rack = null;
  435.       String host = null;
  436.       if (racks != null) {
  437.         rack = racks[idx];
  438.       }
  439.       if (hosts != null) {
  440.         host = hosts[idx];
  441.       }
  442.       
  443.       startTaskTracker(host, rack, idx, numDir);
  444.     }
  445.     this.job = createJobConf(conf);
  446.     waitUntilIdle();
  447.   }
  448.     
  449.   /**
  450.    * Get the task completion events
  451.    */
  452.   public TaskCompletionEvent[] getTaskCompletionEvents(JobID id, int from, 
  453.                                                           int max) 
  454.   throws IOException {
  455.     return jobTracker.getJobTracker().getTaskCompletionEvents(id, from, max);
  456.   }
  457.   /**
  458.    * Change the job's priority
  459.    */
  460.   public void setJobPriority(JobID jobId, JobPriority priority) {
  461.     jobTracker.getJobTracker().setJobPriority(jobId, priority);
  462.   }
  463.   /**
  464.    * Get the job's priority
  465.    */
  466.   public JobPriority getJobPriority(JobID jobId) {
  467.     return jobTracker.getJobTracker().getJob(jobId).getPriority();
  468.   }
  469.   /**
  470.    * Get the job finish time
  471.    */
  472.   public long getJobFinishTime(JobID jobId) {
  473.     return jobTracker.getJobTracker().getJob(jobId).getFinishTime();
  474.   }
  475.   /**
  476.    * Init the job
  477.    */
  478.   public void initializeJob(JobID jobId) throws IOException {
  479.     JobInProgress job = jobTracker.getJobTracker().getJob(jobId);
  480.     job.initTasks();
  481.   }
  482.   
  483.   /**
  484.    * Get the events list at the tasktracker
  485.    */
  486.   public MapTaskCompletionEventsUpdate 
  487.          getMapTaskCompletionEventsUpdates(int index, JobID jobId, int max) 
  488.   throws IOException {
  489.     String jtId = jobTracker.getJobTracker().getTrackerIdentifier();
  490.     TaskAttemptID dummy = 
  491.       new TaskAttemptID(jtId, jobId.getId(), false, 0, 0);
  492.     return taskTrackerList.get(index).getTaskTracker()
  493.                                      .getMapCompletionEvents(jobId, 0, max, 
  494.                                                              dummy);
  495.   }
  496.   
  497.   /**
  498.    * Get jobtracker conf
  499.    */
  500.   public JobConf getJobTrackerConf() {
  501.     return this.conf;
  502.   }
  503.   
  504.   /**
  505.    * Get num events recovered
  506.    */
  507.   public int getNumEventsRecovered() {
  508.     return jobTracker.getJobTracker().recoveryManager.totalEventsRecovered();
  509.   }
  510.   public int getFaultCount(String hostName) {
  511.     return jobTracker.getJobTracker().getFaultCount(hostName);
  512.   }
  513.   
  514.   /**
  515.    * Start the jobtracker.
  516.    */
  517.   public void startJobTracker() {
  518.     //  Create the JobTracker
  519.     jobTracker = new JobTrackerRunner(conf);
  520.     jobTrackerThread = new Thread(jobTracker);
  521.         
  522.     jobTrackerThread.start();
  523.     while (jobTracker.isActive() && !jobTracker.isUp()) {
  524.       try {                                     // let daemons get started
  525.         Thread.sleep(1000);
  526.       } catch(InterruptedException e) {
  527.       }
  528.     }
  529.         
  530.     // is the jobtracker has started then wait for it to init
  531.     ClusterStatus status = null;
  532.     if (jobTracker.isUp()) {
  533.       status = jobTracker.getJobTracker().getClusterStatus(false);
  534.       while (jobTracker.isActive() && status.getJobTrackerState() 
  535.              == JobTracker.State.INITIALIZING) {
  536.         try {
  537.           LOG.info("JobTracker still initializing. Waiting.");
  538.           Thread.sleep(1000);
  539.         } catch(InterruptedException e) {}
  540.         status = jobTracker.getJobTracker().getClusterStatus(false);
  541.       }
  542.     }
  543.     if (!jobTracker.isActive()) {
  544.       // return if jobtracker has crashed
  545.       return;
  546.     }
  547.  
  548.     // Set the configuration for the task-trackers
  549.     this.jobTrackerPort = jobTracker.getJobTrackerPort();
  550.     this.jobTrackerInfoPort = jobTracker.getJobTrackerInfoPort();
  551.   }
  552.   /**
  553.    * Kill the jobtracker.
  554.    */
  555.   public void stopJobTracker() {
  556.     //jobTracker.exit(-1);
  557.     jobTracker.shutdown();
  558.     jobTrackerThread.interrupt();
  559.     try {
  560.       jobTrackerThread.join();
  561.     } catch (InterruptedException ex) {
  562.       LOG.error("Problem waiting for job tracker to finish", ex);
  563.     }
  564.   }
  565.   /**
  566.    * Kill the tasktracker.
  567.    */
  568.   public void stopTaskTracker(int id) {
  569.     TaskTrackerRunner tracker = taskTrackerList.remove(id);
  570.     tracker.shutdown();
  571.     Thread thread = taskTrackerThreadList.remove(id);
  572.     thread.interrupt();
  573.     
  574.     try {
  575.       thread.join();
  576.       // This will break the wait until idle loop
  577.       tracker.isDead = true;
  578.       --numTaskTrackers;
  579.     } catch (InterruptedException ex) {
  580.       LOG.error("Problem waiting for task tracker to finish", ex);
  581.     }
  582.   }
  583.   
  584.   /**
  585.    * Start the tasktracker.
  586.    */
  587.   public void startTaskTracker(String host, String rack, int idx, int numDir) 
  588.   throws IOException {
  589.     if (rack != null) {
  590.       StaticMapping.addNodeToRack(host, rack);
  591.     }
  592.     if (host != null) {
  593.       NetUtils.addStaticResolution(host, "localhost");
  594.     }
  595.     TaskTrackerRunner taskTracker;
  596.     taskTracker = new TaskTrackerRunner(idx, numDir, host, conf);
  597.     
  598.     Thread taskTrackerThread = new Thread(taskTracker);
  599.     taskTrackerList.add(taskTracker);
  600.     taskTrackerThreadList.add(taskTrackerThread);
  601.     taskTrackerThread.start();
  602.     ++numTaskTrackers;
  603.   }
  604.   
  605.   /**
  606.    * Get the tasktrackerID in MiniMRCluster with given trackerName.
  607.    */
  608.   int getTaskTrackerID(String trackerName) {
  609.     for (int id=0; id < numTaskTrackers; id++) {
  610.       if (taskTrackerList.get(id).getTaskTracker().getName().equals(
  611.           trackerName)) {
  612.         return id;
  613.       }
  614.     }
  615.     return -1;
  616.   }
  617.   
  618.   /**
  619.    * Shut down the servers.
  620.    */
  621.   public void shutdown() {
  622.     try {
  623.       waitTaskTrackers();
  624.       for (int idx = 0; idx < numTaskTrackers; idx++) {
  625.         TaskTrackerRunner taskTracker = taskTrackerList.get(idx);
  626.         Thread taskTrackerThread = taskTrackerThreadList.get(idx);
  627.         taskTracker.shutdown();
  628.         taskTrackerThread.interrupt();
  629.         try {
  630.           taskTrackerThread.join();
  631.         } catch (InterruptedException ex) {
  632.           LOG.error("Problem shutting down task tracker", ex);
  633.         }
  634.       }
  635.       stopJobTracker();
  636.     } finally {
  637.       File configDir = new File("build", "minimr");
  638.       File siteFile = new File(configDir, "mapred-site.xml");
  639.       siteFile.delete();
  640.     }
  641.   }
  642.     
  643.   public static void main(String[] args) throws IOException {
  644.     LOG.info("Bringing up Jobtracker and tasktrackers.");
  645.     MiniMRCluster mr = new MiniMRCluster(4, "file:///", 1);
  646.     LOG.info("JobTracker and TaskTrackers are up.");
  647.     mr.shutdown();
  648.     LOG.info("JobTracker and TaskTrackers brought down.");
  649.   }
  650. }