001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.task;
018
019import static org.opends.messages.AdminToolMessages.*;
020import static org.opends.server.config.ConfigConstants.ATTR_BACKEND_BASE_DN;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030import java.util.SortedSet;
031import java.util.TreeSet;
032import java.util.concurrent.atomic.AtomicReference;
033
034import javax.swing.SwingUtilities;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.opendj.config.server.ConfigException;
038import org.forgerock.opendj.ldap.AttributeDescription;
039import org.forgerock.opendj.ldap.DN;
040import org.forgerock.opendj.ldap.Entry;
041import org.forgerock.opendj.ldap.LinkedAttribute;
042import org.forgerock.opendj.ldap.LinkedHashMapEntry;
043import org.forgerock.opendj.ldap.schema.AttributeType;
044import org.forgerock.opendj.ldap.schema.CoreSchema;
045import org.forgerock.opendj.ldap.schema.Schema;
046import org.forgerock.opendj.server.config.client.PluggableBackendCfgClient;
047import org.forgerock.opendj.server.config.client.ReplicationDomainCfgClient;
048import org.forgerock.opendj.server.config.client.ReplicationSynchronizationProviderCfgClient;
049import org.forgerock.opendj.server.config.client.RootCfgClient;
050import org.forgerock.opendj.server.config.server.ReplicationDomainCfg;
051import org.forgerock.opendj.server.config.server.ReplicationSynchronizationProviderCfg;
052import org.forgerock.opendj.server.config.server.RootCfg;
053import org.opends.admin.ads.util.ConnectionWrapper;
054import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
055import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
056import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
057import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
058import org.opends.guitools.controlpanel.ui.ProgressDialog;
059import org.opends.guitools.controlpanel.util.Utilities;
060import org.opends.server.config.ConfigurationHandler;
061import org.opends.server.core.DirectoryServer;
062import org.opends.server.types.DirectoryException;
063import org.opends.server.types.OpenDsException;
064
065/** The task used to delete a set of base DNs or backends. */
066public class DeleteBaseDNAndBackendTask extends Task
067{
068  private Set<String> backendSet;
069  private Map<String, Set<BaseDNDescriptor>> baseDNsToDelete = new HashMap<>();
070  private ArrayList<BackendDescriptor> backendsToDelete = new ArrayList<>();
071
072  /**
073   * Constructor of the task.
074   * @param info the control panel information.
075   * @param dlg the progress dialog where the task progress will be displayed.
076   * @param backendsToDelete the backends to delete.
077   * @param baseDNsToDelete the base DNs to delete.
078   */
079  public DeleteBaseDNAndBackendTask(ControlPanelInfo info, ProgressDialog dlg,
080      Collection<BackendDescriptor> backendsToDelete,
081      Collection<BaseDNDescriptor> baseDNsToDelete)
082  {
083    super(info, dlg);
084    backendSet = new HashSet<>();
085    for (BackendDescriptor backend : backendsToDelete)
086    {
087      backendSet.add(backend.getBackendID());
088    }
089    for (BaseDNDescriptor baseDN : baseDNsToDelete)
090    {
091      backendSet.add(baseDN.getBackend().getBackendID());
092    }
093    for (BaseDNDescriptor baseDN : baseDNsToDelete)
094    {
095      String backendID = baseDN.getBackend().getBackendID();
096      Set<BaseDNDescriptor> set = this.baseDNsToDelete.get(backendID);
097      if (set == null)
098      {
099        set = new HashSet<>();
100        this.baseDNsToDelete.put(backendID, set);
101      }
102      set.add(baseDN);
103    }
104    ArrayList<String> indirectBackendsToDelete = new ArrayList<>();
105    for (Set<BaseDNDescriptor> set : this.baseDNsToDelete.values())
106    {
107      BackendDescriptor backend = set.iterator().next().getBackend();
108      if (set.size() == backend.getBaseDns().size())
109      {
110        // All of the suffixes must be deleted.
111        indirectBackendsToDelete.add(backend.getBackendID());
112        this.backendsToDelete.add(backend);
113      }
114    }
115    for (String backendID : indirectBackendsToDelete)
116    {
117      this.baseDNsToDelete.remove(backendID);
118    }
119    this.backendsToDelete.addAll(backendsToDelete);
120  }
121
122  @Override
123  public Type getType()
124  {
125    return !baseDNsToDelete.isEmpty() ? Type.DELETE_BASEDN : Type.DELETE_BACKEND;
126  }
127
128  @Override
129  public Set<String> getBackends()
130  {
131    return backendSet;
132  }
133
134  @Override
135  public LocalizableMessage getTaskDescription()
136  {
137    StringBuilder sb = new StringBuilder();
138
139    if (!baseDNsToDelete.isEmpty())
140    {
141      ArrayList<String> dns = new ArrayList<>();
142      for (Set<BaseDNDescriptor> set : baseDNsToDelete.values())
143      {
144        for (BaseDNDescriptor baseDN : set)
145        {
146          dns.add(baseDN.getDn().toString());
147        }
148      }
149      if (dns.size() == 1)
150      {
151        String dn = dns.iterator().next();
152        sb.append(INFO_CTRL_PANEL_DELETE_BASE_DN_DESCRIPTION.get(dn));
153      }
154      else
155      {
156        ArrayList<String> quotedDns = new ArrayList<>();
157        for (String dn : dns)
158        {
159          quotedDns.add("'"+dn+"'");
160        }
161        sb.append(INFO_CTRL_PANEL_DELETE_BASE_DNS_DESCRIPTION.get(
162        Utilities.getStringFromCollection(quotedDns, ", ")));
163      }
164    }
165
166    if (!backendsToDelete.isEmpty())
167    {
168      if (sb.length() > 0)
169      {
170        sb.append("  ");
171      }
172      if (backendsToDelete.size() == 1)
173      {
174        sb.append(INFO_CTRL_PANEL_DELETE_BACKEND_DESCRIPTION.get(
175            backendsToDelete.iterator().next().getBackendID()));
176      }
177      else
178      {
179        ArrayList<String> ids = new ArrayList<>();
180        for (BackendDescriptor backend : backendsToDelete)
181        {
182          ids.add(backend.getBackendID());
183        }
184        sb.append(INFO_CTRL_PANEL_DELETE_BACKENDS_DESCRIPTION.get(
185        Utilities.getStringFromCollection(ids, ", ")));
186      }
187    }
188    return LocalizableMessage.raw(sb.toString());
189  }
190
191  @Override
192  public boolean canLaunch(Task taskToBeLaunched,
193      Collection<LocalizableMessage> incompatibilityReasons)
194  {
195    boolean canLaunch = true;
196    if (state == State.RUNNING && runningOnSameServer(taskToBeLaunched))
197    {
198      // All the operations are incompatible if they apply to this
199      // backend for safety.  This is a short operation so the limitation
200      // has not a lot of impact.
201      Set<String> backends = new TreeSet<>(taskToBeLaunched.getBackends());
202      backends.retainAll(getBackends());
203      if (!backends.isEmpty())
204      {
205        incompatibilityReasons.add(
206            getIncompatibilityMessage(this, taskToBeLaunched));
207        canLaunch = false;
208      }
209    }
210    return canLaunch;
211  }
212
213  /**
214   * Update the configuration in the server.
215   * @throws OpenDsException if an error occurs.
216   */
217  private void updateConfiguration() throws Exception
218  {
219    boolean configHandlerUpdated = false;
220    final int totalNumber = baseDNsToDelete.size() + backendsToDelete.size();
221    int numberDeleted = 0;
222    try
223    {
224      if (!isServerRunning())
225      {
226        configHandlerUpdated = true;
227        stopPoolingAndInitializeConfiguration();
228      }
229      boolean isFirst = true;
230      for (final Set<BaseDNDescriptor> baseDNs : baseDNsToDelete.values())
231      {
232        if (!isFirst)
233        {
234          SwingUtilities.invokeLater(new Runnable()
235          {
236            @Override
237            public void run()
238            {
239              getProgressDialog().appendProgressHtml("<br><br>");
240            }
241          });
242        }
243        isFirst = false;
244
245        for (BaseDNDescriptor baseDN : baseDNs)
246        {
247          disableReplicationIfRequired(baseDN);
248        }
249
250        if (isServerRunning())
251        {
252          SwingUtilities.invokeLater(new Runnable()
253          {
254            @Override
255            public void run()
256            {
257              List<String> args =
258                getObfuscatedCommandLineArguments(
259                    getDSConfigCommandLineArguments(baseDNs));
260              args.removeAll(getConfigCommandLineArguments());
261              printEquivalentCommandLine(getConfigCommandLinePath(), args,
262                  INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_BASE_DN.get());
263            }
264          });
265        }
266        SwingUtilities.invokeLater(new Runnable()
267        {
268          @Override
269          public void run()
270          {
271            if (baseDNs.size() == 1)
272            {
273              String dn = baseDNs.iterator().next().getDn().toString();
274              getProgressDialog().appendProgressHtml(
275                  Utilities.getProgressWithPoints(
276                      INFO_CTRL_PANEL_DELETING_BASE_DN.get(dn),
277                      ColorAndFontConstants.progressFont));
278            }
279            else
280            {
281              ArrayList<String> dns = new ArrayList<>();
282              for (BaseDNDescriptor baseDN : baseDNs)
283              {
284                dns.add("'" + baseDN.getDn() + "'");
285              }
286              getProgressDialog().appendProgressHtml(
287                  Utilities.getProgressWithPoints(
288                      INFO_CTRL_PANEL_DELETING_BASE_DNS.get(
289                      Utilities.getStringFromCollection(dns, ", ")),
290                      ColorAndFontConstants.progressFont));
291            }
292          }
293        });
294        if (isServerRunning())
295        {
296          deleteBaseDNs(getInfo().getConnection(), baseDNs);
297        }
298        else
299        {
300          deleteBaseDNs(baseDNs);
301        }
302        numberDeleted ++;
303        final int fNumberDeleted = numberDeleted;
304        SwingUtilities.invokeLater(new Runnable()
305        {
306          @Override
307          public void run()
308          {
309            getProgressDialog().getProgressBar().setIndeterminate(false);
310            getProgressDialog().getProgressBar().setValue(
311                (fNumberDeleted * 100) / totalNumber);
312            getProgressDialog().appendProgressHtml(
313                Utilities.getProgressDone(ColorAndFontConstants.progressFont));
314          }
315        });
316      }
317      for (final BackendDescriptor backend : backendsToDelete)
318      {
319        if (!isFirst)
320        {
321          SwingUtilities.invokeLater(new Runnable()
322          {
323            @Override
324            public void run()
325            {
326              getProgressDialog().appendProgressHtml("<br><br>");
327            }
328          });
329        }
330        for (BaseDNDescriptor baseDN : backend.getBaseDns())
331        {
332          disableReplicationIfRequired(baseDN);
333        }
334        isFirst = false;
335        if (isServerRunning())
336        {
337          SwingUtilities.invokeLater(new Runnable()
338          {
339            @Override
340            public void run()
341            {
342              List<String> args =
343                getObfuscatedCommandLineArguments(
344                    getDSConfigCommandLineArguments(backend));
345              args.removeAll(getConfigCommandLineArguments());
346              printEquivalentCommandLine(getConfigCommandLinePath(), args,
347                  INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_BACKEND.get());
348            }
349          });
350        }
351        SwingUtilities.invokeLater(new Runnable()
352        {
353          @Override
354          public void run()
355          {
356            getProgressDialog().appendProgressHtml(
357                  Utilities.getProgressWithPoints(
358                      INFO_CTRL_PANEL_DELETING_BACKEND.get(
359                          backend.getBackendID()),
360                      ColorAndFontConstants.progressFont));
361          }
362        });
363        if (isServerRunning())
364        {
365          deleteBackend(getInfo().getConnection(), backend);
366        }
367        else
368        {
369          deleteBackend(backend);
370        }
371        numberDeleted ++;
372        final int fNumberDeleted = numberDeleted;
373        SwingUtilities.invokeLater(new Runnable()
374        {
375          @Override
376          public void run()
377          {
378            getProgressDialog().getProgressBar().setIndeterminate(false);
379            getProgressDialog().getProgressBar().setValue(
380                (fNumberDeleted * 100) / totalNumber);
381            getProgressDialog().appendProgressHtml(
382                Utilities.getProgressDone(ColorAndFontConstants.progressFont));
383          }
384        });
385      }
386    }
387    finally
388    {
389      if (configHandlerUpdated)
390      {
391        startPoolingAndInitializeConfiguration();
392      }
393    }
394  }
395
396  /**
397   * Deletes a set of base DNs.  The code assumes that the server is not running
398   * and that the configuration file can be edited.
399   * @param baseDNs the list of base DNs.
400   * @throws OpenDsException if an error occurs.
401   */
402  private void deleteBaseDNs(Set<BaseDNDescriptor> baseDNs)
403  throws OpenDsException, ConfigException
404  {
405    BackendDescriptor backend = baseDNs.iterator().next().getBackend();
406
407    SortedSet<DN> oldBaseDNs = new TreeSet<>();
408    for (BaseDNDescriptor baseDN : backend.getBaseDns())
409    {
410      oldBaseDNs.add(baseDN.getDn());
411    }
412    LinkedList<DN> newBaseDNs = new LinkedList<>(oldBaseDNs);
413    ArrayList<DN> dnsToRemove = new ArrayList<>();
414    for (BaseDNDescriptor baseDN : baseDNs)
415    {
416      dnsToRemove.add(baseDN.getDn());
417    }
418    newBaseDNs.removeAll(dnsToRemove);
419
420    String backendName = backend.getBackendID();
421    DN dn = DN.valueOf("ds-cfg-backend-id=" + backendName + ",cn=Backends,cn=config");
422    updateConfigEntryWithAttribute(dn, ATTR_BACKEND_BASE_DN, newBaseDNs);
423  }
424
425  /** Update a config entry with the provided attribute parameters. */
426  private void updateConfigEntryWithAttribute(DN entryDn, String attrName, List<DN> newBaseDNs)
427      throws DirectoryException, ConfigException
428  {
429    ConfigurationHandler configHandler = DirectoryServer.getConfigurationHandler();
430    final Entry configEntry = configHandler.getEntry(entryDn);
431    final Entry newEntry = LinkedHashMapEntry.deepCopyOfEntry(configEntry);
432    AttributeType attrType = Schema.getDefaultSchema().getAttributeType(
433        attrName, CoreSchema.getDirectoryStringSyntax());
434    newEntry.replaceAttribute(new LinkedAttribute(AttributeDescription.create(attrType), newBaseDNs));
435    configHandler.replaceEntry(configEntry, newEntry);
436  }
437
438  /**
439   * Deletes a set of base DNs.  The code assumes that the server is running
440   * and that the provided connection is active.
441   * @param baseDNs the list of base DNs.
442   * @param connWrapper the connection to the server.
443   * @throws OpenDsException if an error occurs.
444   */
445  private void deleteBaseDNs(ConnectionWrapper connWrapper,
446      Set<BaseDNDescriptor> baseDNs) throws Exception
447  {
448    RootCfgClient root = connWrapper.getRootConfiguration();
449    PluggableBackendCfgClient backend =
450      (PluggableBackendCfgClient)root.getBackend(
451          baseDNs.iterator().next().getBackend().getBackendID());
452    SortedSet<DN> oldBaseDNs = backend.getBaseDN();
453    SortedSet<DN> newBaseDNs = new TreeSet<>(oldBaseDNs);
454    ArrayList<DN> dnsToRemove = new ArrayList<>();
455    for (BaseDNDescriptor baseDN : baseDNs)
456    {
457      dnsToRemove.add(baseDN.getDn());
458    }
459    newBaseDNs.removeAll(dnsToRemove);
460    backend.setBaseDN(newBaseDNs);
461    backend.commit();
462  }
463
464  /**
465   * Deletes a backend.  The code assumes that the server is not running
466   * and that the configuration file can be edited.
467   * @param backend the backend to be deleted.
468   * @throws OpenDsException if an error occurs.
469   */
470  private void deleteBackend(BackendDescriptor backend) throws OpenDsException, ConfigException
471  {
472    DN dn = DN.valueOf("ds-cfg-backend-id" + "=" + backend.getBackendID() + ",cn=Backends,cn=config");
473    Utilities.deleteConfigSubtree(DirectoryServer.getConfigurationHandler(), dn);
474  }
475
476  /**
477   * Deletes a backend.  The code assumes that the server is running
478   * and that the provided connection is active.
479   * @param backend the backend to be deleted.
480   * @param connWrapper the connection to the server.
481   * @throws OpenDsException if an error occurs.
482   */
483  private void deleteBackend(ConnectionWrapper connWrapper,
484      BackendDescriptor backend) throws Exception
485  {
486    RootCfgClient root = connWrapper.getRootConfiguration();
487    root.removeBackend(backend.getBackendID());
488    root.commit();
489  }
490
491  @Override
492  protected String getCommandLinePath()
493  {
494    return null;
495  }
496
497  @Override
498  protected ArrayList<String> getCommandLineArguments()
499  {
500    return new ArrayList<>();
501  }
502
503  /**
504   * Returns the path of the command line to be used.
505   *
506   * @return the path of the command line to be used
507   */
508  private String getConfigCommandLinePath()
509  {
510    if (isServerRunning())
511    {
512      return getCommandLinePath("dsconfig");
513    }
514    return null;
515  }
516
517  @Override
518  public void runTask()
519  {
520    state = State.RUNNING;
521    lastException = null;
522
523    try
524    {
525      updateConfiguration();
526      state = State.FINISHED_SUCCESSFULLY;
527    }
528    catch (Throwable t)
529    {
530      lastException = t;
531      state = State.FINISHED_WITH_ERROR;
532    }
533  }
534
535  /**
536   * Return the dsconfig arguments required to delete a set of base DNs.
537   * @param baseDNs the base DNs to be deleted.
538   * @return the dsconfig arguments required to delete a set of base DNs.
539   */
540  private ArrayList<String> getDSConfigCommandLineArguments(
541      Set<BaseDNDescriptor> baseDNs)
542  {
543    ArrayList<String> args = new ArrayList<>();
544    if (isServerRunning())
545    {
546      args.add("set-backend-prop");
547      args.add("--backend-name");
548      args.add(baseDNs.iterator().next().getBackend().getBackendID());
549      args.add("--remove");
550      for (BaseDNDescriptor baseDN : baseDNs)
551      {
552        args.add("base-dn:" + baseDN.getDn());
553      }
554      args.addAll(getConnectionCommandLineArguments());
555      args.add("--no-prompt");
556    }
557    return args;
558  }
559
560  /**
561   * Return the dsconfig arguments required to delete a backend.
562   * @param backend the backend to be deleted.
563   * @return the dsconfig arguments required to delete a backend.
564   */
565  private ArrayList<String> getDSConfigCommandLineArguments(
566      BackendDescriptor backend)
567  {
568    ArrayList<String> args = new ArrayList<>();
569    args.add("delete-backend");
570    args.add("--backend-name");
571    args.add(backend.getBackendID());
572
573    args.addAll(getConnectionCommandLineArguments());
574    args.add("--no-prompt");
575    return args;
576  }
577
578  /**
579   * Disables replication if required: if the deleted base DN is replicated,
580   * update the replication configuration to remove any reference to it.
581   * @param baseDN the base DN that is going to be removed.
582   * @throws OpenDsException if an error occurs.
583   */
584  private void disableReplicationIfRequired(final BaseDNDescriptor baseDN)
585  throws Exception
586  {
587    if (baseDN.getType() == BaseDNDescriptor.Type.REPLICATED)
588    {
589      final AtomicReference<String> domainName = new AtomicReference<>();
590
591      try
592      {
593        if (isServerRunning())
594        {
595          ConnectionWrapper connWrapper = getInfo().getConnection();
596          RootCfgClient root = connWrapper.getRootConfiguration();
597          ReplicationSynchronizationProviderCfgClient sync = null;
598          try
599          {
600            sync = (ReplicationSynchronizationProviderCfgClient)
601            root.getSynchronizationProvider("Multimaster Synchronization");
602          }
603          catch (Exception oe)
604          {
605            // Ignore this one
606          }
607          if (sync != null)
608          {
609            String[] domains = sync.listReplicationDomains();
610            if (domains != null)
611            {
612              for (String dName : domains)
613              {
614                ReplicationDomainCfgClient domain = sync.getReplicationDomain(dName);
615                if (baseDN.getDn().equals(domain.getBaseDN()))
616                {
617                  domainName.set(dName);
618                  sync.removeReplicationDomain(dName);
619                  sync.commit();
620                  break;
621                }
622              }
623            }
624          }
625        }
626        else
627        {
628          RootCfg root = DirectoryServer.getInstance().getServerContext().getRootConfig();
629          ReplicationSynchronizationProviderCfg sync = null;
630          try
631          {
632            sync = (ReplicationSynchronizationProviderCfg)
633            root.getSynchronizationProvider("Multimaster Synchronization");
634          }
635          catch (ConfigException oe)
636          {
637            // Ignore this one
638          }
639          if (sync != null)
640          {
641            String[] domains = sync.listReplicationDomains();
642            if (domains != null)
643            {
644              for (String dName : domains)
645              {
646                ReplicationDomainCfg domain = sync.getReplicationDomain(dName);
647                DN dn = domain.getBaseDN();
648                if (dn.equals(baseDN.getDn()))
649                {
650                  domainName.set(dName);
651                  DN entryDN = domain.dn();
652                  Utilities.deleteConfigSubtree(DirectoryServer.getConfigurationHandler(), entryDN);
653                  break;
654                }
655              }
656            }
657          }
658        }
659      }
660      finally
661      {
662        // This is not super clean, but this way we calculate the domain name only once.
663        if (isServerRunning() && domainName.get() != null)
664        {
665          SwingUtilities.invokeLater(new Runnable()
666          {
667            @Override
668            public void run()
669            {
670              List<String> args =
671                getObfuscatedCommandLineArguments(
672                    getCommandLineArgumentsToDisableReplication(domainName.get()));
673              args.removeAll(getConfigCommandLineArguments());
674              args.add(getNoPropertiesFileArgument());
675              printEquivalentCommandLine(getConfigCommandLinePath(), args,
676                  INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_DOMAIN.get(baseDN.getDn()));
677              }
678          });
679        }
680        SwingUtilities.invokeLater(new Runnable()
681        {
682          @Override
683          public void run()
684          {
685            getProgressDialog().appendProgressHtml(
686                Utilities.getProgressWithPoints(
687                    INFO_CTRL_PANEL_DELETING_DOMAIN.get(baseDN.getDn()),
688                    ColorAndFontConstants.progressFont));
689          }
690        });
691      }
692      SwingUtilities.invokeLater(new Runnable()
693      {
694        @Override
695        public void run()
696        {
697          getProgressDialog().appendProgressHtml(
698              Utilities.getProgressDone(ColorAndFontConstants.progressFont)+
699              "<br>");
700        }
701      });
702    }
703  }
704
705  /**
706   * Return the dsconfig arguments required to delete a replication domain.
707   * @param domainName the name of the domain to be deleted.
708   * @return the dsconfig arguments required to delete a replication domain.
709   */
710  private ArrayList<String> getCommandLineArgumentsToDisableReplication(
711      String domainName)
712  {
713    ArrayList<String> args = new ArrayList<>();
714    args.add("delete-replication-domain");
715    args.add("--provider-name");
716    args.add("Multimaster Synchronization");
717    args.add("--domain-name");
718    args.add(domainName);
719    args.addAll(getConnectionCommandLineArguments());
720    args.add("--no-prompt");
721    return args;
722  }
723}