Hello community,

here is the log from the commit of package yast2-cluster for openSUSE:Factory 
checked in at 2018-01-06 18:50:50
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yast2-cluster (Old)
 and      /work/SRC/openSUSE:Factory/.yast2-cluster.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "yast2-cluster"

Sat Jan  6 18:50:50 2018 rev:21 rq:561811 version:4.0.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/yast2-cluster/yast2-cluster.changes      
2017-12-05 01:30:19.469110258 +0100
+++ /work/SRC/openSUSE:Factory/.yast2-cluster.new/yast2-cluster.changes 
2018-01-06 18:51:01.086104448 +0100
@@ -1,0 +2,6 @@
+Fri Dec 22 07:04:15 UTC 2017 - nw...@suse.com
+
+- bsc#1070961, support corosync qdevice.
+- Version 4.0.2
+
+-------------------------------------------------------------------

Old:
----
  yast2-cluster-4.0.1.tar.bz2

New:
----
  yast2-cluster-4.0.2.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ yast2-cluster.spec ++++++
--- /var/tmp/diff_new_pack.JqlsHe/_old  2018-01-06 18:51:01.662077536 +0100
+++ /var/tmp/diff_new_pack.JqlsHe/_new  2018-01-06 18:51:01.666077349 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package yast2-cluster
 #
-# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 Name:           yast2-cluster
 %define _fwdefdir /etc/sysconfig/SuSEfirewall2.d/services
-Version:        4.0.1
+Version:        4.0.2
 Release:        0
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build

++++++ cluster.fwd ++++++
--- /var/tmp/diff_new_pack.JqlsHe/_old  2018-01-06 18:51:01.702075667 +0100
+++ /var/tmp/diff_new_pack.JqlsHe/_new  2018-01-06 18:51:01.702075667 +0100
@@ -6,6 +6,7 @@
 # 5560 for mgmtd
 # 7630 for hawk or hawk2
 # 21064 for dlm
+# 5403 for corosync qdevice(default)
 TCP="30865 5560 7630 21064"
 
 # space separated list of allowed UDP ports

++++++ yast2-cluster-4.0.1.tar.bz2 -> yast2-cluster-4.0.2.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/package/cluster.fwd 
new/yast2-cluster-4.0.2/package/cluster.fwd
--- old/yast2-cluster-4.0.1/package/cluster.fwd 2017-12-04 10:34:09.000000000 
+0100
+++ new/yast2-cluster-4.0.2/package/cluster.fwd 2018-01-05 10:40:16.000000000 
+0100
@@ -6,6 +6,7 @@
 # 5560 for mgmtd
 # 7630 for hawk or hawk2
 # 21064 for dlm
+# 5403 for corosync qdevice(default)
 TCP="30865 5560 7630 21064"
 
 # space separated list of allowed UDP ports
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/package/yast2-cluster.changes 
new/yast2-cluster-4.0.2/package/yast2-cluster.changes
--- old/yast2-cluster-4.0.1/package/yast2-cluster.changes       2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/package/yast2-cluster.changes       2018-01-05 
10:40:16.000000000 +0100
@@ -1,4 +1,10 @@
 -------------------------------------------------------------------
+Fri Dec 22 07:04:15 UTC 2017 - nw...@suse.com
+
+- bsc#1070961, support corosync qdevice.
+- Version 4.0.2
+
+-------------------------------------------------------------------
 Fri Dec  1 06:08:41 UTC 2017 - nw...@suse.com
 
 - fate#323526, support python3.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/package/yast2-cluster.spec 
new/yast2-cluster-4.0.2/package/yast2-cluster.spec
--- old/yast2-cluster-4.0.1/package/yast2-cluster.spec  2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/package/yast2-cluster.spec  2018-01-05 
10:40:16.000000000 +0100
@@ -18,7 +18,7 @@
 
 Name:           yast2-cluster
 %define _fwdefdir /etc/sysconfig/SuSEfirewall2.d/services
-Version:        4.0.1
+Version:        4.0.2
 Release:        0
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/src/include/cluster/common.rb 
new/yast2-cluster-4.0.2/src/include/cluster/common.rb
--- old/yast2-cluster-4.0.1/src/include/cluster/common.rb       2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/src/include/cluster/common.rb       2018-01-05 
10:40:16.000000000 +0100
@@ -39,16 +39,17 @@
       Yast.import "SuSEFirewall"
       Yast.import "SuSEFirewallServices"
 
-      @DIALOG = ["communication", "security", "csync2", "conntrack", "service"]
+      @DIALOG = ["communication", "corosyncqdevice", "security", "csync2", 
"conntrack", "service"]
 
       @PARENT = {}
 
       @NAME = {
-        "communication" => _("Communication Channels"),
-        "security"      => _("Security"),
-        "service"       => _("Service"),
-        "csync2"        => _("Configure Csync2"),
-        "conntrack"     => _("Configure conntrackd")
+        "communication"    => _("Communication Channels"),
+        "corosyncqdevice"  => _("Corosync Qdevice"),
+        "security"         => _("Security"),
+        "service"          => _("Service"),
+        "csync2"           => _("Configure Csync2"),
+        "conntrack"        => _("Configure conntrackd")
       }
     end
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/src/include/cluster/dialogs.rb 
new/yast2-cluster-4.0.2/src/include/cluster/dialogs.rb
--- old/yast2-cluster-4.0.1/src/include/cluster/dialogs.rb      2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/src/include/cluster/dialogs.rb      2018-01-05 
10:40:16.000000000 +0100
@@ -161,7 +161,7 @@
       idset = Set[]
 
       Builtins.foreach(Cluster.memberaddr) do |value|
-        if  value[:nodeid].to_i <= 0
+        if value[:nodeid].to_i <= 0
           Popup.Message(_("Node ID has to be fulfilled with a positive 
integer"))
           UI.ChangeWidget(:memberaddr, :CurrentItem, i)
           i = 0
@@ -529,7 +529,7 @@
       UI.ChangeWidget(Id(:autoid), :Value, Cluster.autoid)
       UI.ChangeWidget(Id(:cluster_name), :Value, Cluster.cluster_name)
       UI.ChangeWidget(Id(:expected_votes), :Value, Cluster.expected_votes)
-      UI.ChangeWidget(:expected_votes, :ValidChars, "0123456789" );
+      UI.ChangeWidget(:expected_votes, :ValidChars, "0123456789");
 
       UI.ChangeWidget(Id(:transport), :Value, Cluster.transport)
 
@@ -708,6 +708,205 @@
       nil
     end
 
+    def ValidateCorosyncQdevice
+      if UI.QueryWidget(Id(:corosync_qdevice), :Value) == false
+        return true
+      end
+
+      if UI.QueryWidget(Id(:qdevice_model), :Value) != "net"
+        Popup.Message(_("The model basically defines the type of arbitrator, 
currently only net is support"))
+        UI.SetFocus(:qdevice_model)
+        return false
+      end
+
+      if UI.QueryWidget(Id(:qdevice_votes), :Value).to_i <= 0
+        Popup.Message(_("Qdevice votes must be a positive integer"))
+        UI.SetFocus(:qdevice_votes)
+        return false
+      end
+
+      if !IP.Check(UI.QueryWidget(Id(:qdevice_host), :Value))
+        Popup.Message(_("Qdevice host mush have a valid IP address"))
+        UI.SetFocus(:qdevice_host)
+        return false
+      end
+
+      if UI.QueryWidget(Id(:qdevice_port), :Value).to_i <= 0
+        Popup.Message(_("The corosync qdevice port must be a positive 
integer"))
+        UI.SetFocus(Id(:qdevice_port))
+        return false
+      end
+
+      if !["ffsplit", "lms", "test", 
"2nodelms"].include?(UI.QueryWidget(Id(:qdevice_algorithm), :Value))
+        Popup.Message(_("The algorithm only can be one of the ffsplit, lms, 
test or 2nodelms." \
+          "YaST will overwrite test and 2nodelms."))
+        UI.SetFocus(Id(:algorithm))
+        return false
+      end
+
+      if !["lowest", 
"highest"].include?(UI.QueryWidget(Id(:qdevice_tie_breaker), :Value)) &&
+        (UI.QueryWidget(Id(:qdevice_tie_breaker), :Value).to_i <= 0)
+        Popup.Message(_("The tie breaker can be one of lowest, highest or a 
valid node id (number)"))
+        UI.SetFocus(Id(:qdevice_tie_breaker))
+        return false
+      end
+
+      true
+    end
+
+    def SaveCorosyncQdevice
+      Cluster.corosync_qdevice = 
Convert.to_boolean(UI.QueryWidget(Id(:corosync_qdevice), :Value))
+
+      Cluster.qdevice_model = UI.QueryWidget(Id(:qdevice_model), :Value)
+      Cluster.qdevice_votes = UI.QueryWidget(Id(:qdevice_votes), :Value).to_s
+
+      Cluster.qdevice_host = UI.QueryWidget(Id(:qdevice_host), :Value)
+      Cluster.qdevice_port = UI.QueryWidget(Id(:qdevice_port), :Value).to_s
+      Cluster.qdevice_tls = UI.QueryWidget(Id(:qdevice_tls), :Value)
+      Cluster.qdevice_algorithm = UI.QueryWidget(Id(:qdevice_algorithm), 
:Value)
+      Cluster.qdevice_tie_breaker = UI.QueryWidget(Id(:qdevice_tie_breaker), 
:Value)
+      nil
+    end
+
+    def CorosyncQdeviceLayout
+      qdevice_section = VBox(
+        HBox(
+          ComboBox(
+            Id(:qdevice_model),
+            Opt(:hstretch),
+            _("Qdevice model:"),
+            ["net"]
+          ),
+          Left(InputField(Id(:qdevice_votes),Opt(:hstretch), _("Qdevice 
votes:"),""))
+        )
+      )
+
+      qdevice_net_section = VBox(
+        HBox(
+          Left(InputField(Id(:qdevice_host),Opt(:hstretch), _("Qnetd server 
host:"),"")),
+          HSpacing(1),
+          Left(InputField(Id(:qdevice_port),Opt(:hstretch), _("Qnetd server 
TCP port:"),"5403"))
+        ),
+        HBox(
+          Left(ComboBox(
+            Id(:qdevice_tls), Opt(:hstretch), _("TLS:"),
+            ["off", "on", "required"]
+          )),
+          Left(ComboBox(
+            Id(:qdevice_algorithm), Opt(:hstretch, :notify), _("Algorithm:"),
+            [
+              Item(Id("ffsplit"), "ffsplit"),
+              Item(Id("lms"), "lms")
+            ]
+          )),
+          HSpacing(1),
+          Left(InputField(Id(:qdevice_tie_breaker),Opt(:hstretch), _("Tie 
breaker:"),"lowest"))
+        )
+      )
+
+      contents = VBox(
+        VSpacing(1),
+        CheckBoxFrame(
+          Id(:corosync_qdevice),
+          Opt(:hstretch, :notify),
+          _("En&able Corosync Qdevice"),
+          false,
+          VBox(
+            VSpacing(1),
+            qdevice_section,
+            VSpacing(2),
+            qdevice_net_section,
+          )
+        ),
+        VStretch()
+      )
+
+      my_SetContents("corosyncqdevice", contents)
+
+      UI.ChangeWidget(Id(:corosync_qdevice), :Value, Cluster.corosync_qdevice)
+
+      UI.ChangeWidget(Id(:qdevice_model), :Value, Cluster.qdevice_model)
+      UI.ChangeWidget(Id(:qdevice_votes), :Value, Cluster.qdevice_votes)
+      UI.ChangeWidget(Id(:qdevice_votes), :ValidChars, "0123456789");
+
+      UI.ChangeWidget(Id(:qdevice_host), :Value, Cluster.qdevice_host)
+      UI.ChangeWidget(Id(:qdevice_port), :Value, Cluster.qdevice_port)
+      UI.ChangeWidget(Id(:qdevice_tls), :Value, Cluster.qdevice_tls)
+      UI.ChangeWidget(Id(:qdevice_algorithm), :Value, 
Cluster.qdevice_algorithm)
+      UI.ChangeWidget(Id(:qdevice_tie_breaker), :Value, 
Cluster.qdevice_tie_breaker)
+
+      nil
+    end
+
+    def UpdateQdeviceVotes
+      is_ffsplit = UI.QueryWidget(Id(:qdevice_algorithm), :Value) == "ffsplit"
+
+      if is_ffsplit
+        UI.ChangeWidget(Id(:qdevice_votes), :Value, "1")
+      end
+
+      UI.ChangeWidget(Id(:qdevice_votes), :Enabled, !is_ffsplit)
+
+      nil
+    end
+
+    def CorosyncQdeviceDialog
+      ret = nil
+
+      CorosyncQdeviceLayout()
+
+      while true
+        UpdateQdeviceVotes()
+
+        ret = UI.UserInput
+
+        if ret == :corosync_qdevice
+          if UI.QueryWidget(Id(:corosync_qdevice), :Value) == false
+            next
+          end
+        end
+
+        if ret == :next || ret == :back
+          val = ValidateCorosyncQdevice()
+          if val == true
+            SaveCorosyncQdevice()
+            break
+          else
+            ret = nil
+            next
+          end
+        end
+
+        if ret == :abort || ret == :cancel
+          if ReallyAbort()
+            return deep_copy(ret)
+          else
+            next
+          end
+        end
+
+        if ret == :wizardTree
+          ret = Convert.to_string(UI.QueryWidget(Id(:wizardTree), 
:CurrentItem))
+        end
+
+        if Builtins.contains(@DIALOG, Convert.to_string(ret))
+          ret = Builtins.symbolof(Builtins.toterm(ret))
+          val = ValidateCorosyncQdevice()
+          if val == true
+            SaveCorosyncQdevice()
+            break
+          else
+            ret = nil
+            Wizard.SelectTreeItem("corosyncqdevice")
+            next
+          end
+        end
+
+        Builtins.y2error("unexpected retcode: %1", ret)
+      end
+      deep_copy(ret)
+    end
+
     def SecurityDialog
       ret = nil
 
@@ -809,7 +1008,6 @@
       true
     end
 
-
     def UpdateServiceStatus
       ret = 0
       ret = Service.Status("pacemaker")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/src/include/cluster/helps.rb 
new/yast2-cluster-4.0.2/src/include/cluster/helps.rb
--- old/yast2-cluster-4.0.1/src/include/cluster/helps.rb        2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/src/include/cluster/helps.rb        2018-01-05 
10:40:16.000000000 +0100
@@ -42,6 +42,15 @@
             "<p><b><big>Expected votes</big></b><br>Expect vote number for 
voting quorum.  Will be automatically calculated when the nodelist {} section 
is present in corosync.conf (the list will be generated when using unicast 
transport) or can be specified in the quorum {} section (Expect votes should 
use the total node numble of the cluster). If Expected votes presents in 
unicast transport, the value will override the one automatically 
calculated.<br></p>\n" +
             "<p><b><big>Auto Generate Node ID</big></b><br>Nodeid is required 
when using IPv6. Auto node ID enabled will generate nodeid 
automatically.<br></p>\n"
         ),
+        "corosyncqdevice"      => _(
+          "<p><b><big>Model</big></b><br>Specifies the model to be used. This 
parameter is required.  corosync-qdevice is modular and is able to support 
multiple different models. The model basically defines what type of arbitrator 
is used. Currently only 'net' is supported.</p>\n" +
+            "<p><b><big>Votes</big></b><br>The number of votes provided to the 
cluster by qdevice. It should be configured as 1 when using 'ffsplit' algorithm 
and sum(votes_per_node) - 1 when 'lms' algorithm. Default is 1 or 
(number_of_nodes - 1) or generally sum(votes_per_node) - 1 based on 
algorithm.</p>\n" +
+            "<p><b><big>Host</big></b><br>Specifies the IP address or host 
name of the qnetd server to be used. This parameter is required.</p>\n" +
+            "<p><b><big>Port</big></b><br>Specifies TCP port of qnetd server. 
Default is 5403.</p>\n" +
+            "<p><b><big>TLS</big></b><br>Can be one of 'on', 'off' or 
'required' and specifies if tls should be used. 'on' means a connection with 
TLS is attempted first, but if the server doesn't advertise TLS support then 
non-TLS will be used. 'off' is used then TLS is not required and it's then not 
even tried. This mode is the only one which doesn't need a properly initialized 
NSS database. 'required' means TLS is required and if the server doesn't 
support TLS, qdevice will exit with error message. 'on' need manually change, 
refer to corosync-qdevice's man page for more details. Default is 'off' in 
yast.</p>\n" +
+            "<p><b><big>Algorithm</big></b><br>Decision algorithm. Can be one 
of the 'ffsplit' or 'lms'.  (Actually there are also 'test' and '2nodelms', 
both of which are mainly for developers and shouldn't be used for production 
clusters, so yast will convert to 'ffsplit' automatically). For a description 
of what each algorithm means and how the algorithms differ see their individual 
sections.  Default value is ffsplit.</p>\n" +
+            "<p><b><big>Tie breaker</big></b><br>Can be one of 'lowest', 
'highest' or 'valid_node_id' (number) values. It's used as a fallback if 
qdevice has to decide between two or more equal partitions. 'lowest' means the 
partition with the lowest node id is chosen. 'highest' means the partition with 
highest node id is chosen. And 'valid_node_id' means that the partition 
containing the node with the given node id is chosen. Default is 
'lowest'.</p>\n"
+        ),
         "security"      => _(
           "\n" +
             "<p><b><big>Enable Security Auth</big></b><br>This  specifies  
that HMAC/SHA1 authentication should be used to authenticate all messages.  It 
further specifies that  all  data should  be  encrypted  with the sober128 
encryption algorithm to protect data from eavesdropping.  Enabling this option 
adds a 36 byte header to every message sent by totem which reduces total 
throughput.  Encryption and authentication consume 75% of CPU cycles in aisexec 
as  measured  with gprof when enabled.  For  100Mbit  networks  with  1500  MTU 
 frame  transmissions: A throughput of 9Mb/s is possible with 100% cpu 
utilization when this  option  is enabled on 3GHz cpus.  A throughput of 10Mb/s 
is possible wth 20% cpu utilization when this option is  disabled on 3GHz cpus. 
 For  gig-e networks with large frame transmissions: A throughput of 20Mb/s is 
possible when this  option  is  enabled  on  3GHz cpus.   A throughput of 
60Mb/s is possible when this option is disabled on 3GHz cpus.  The default is 
on. <br></p>\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/src/include/cluster/wizards.rb 
new/yast2-cluster-4.0.2/src/include/cluster/wizards.rb
--- old/yast2-cluster-4.0.1/src/include/cluster/wizards.rb      2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/src/include/cluster/wizards.rb      2018-01-05 
10:40:16.000000000 +0100
@@ -40,11 +40,12 @@
       Yast.include include_target, "cluster/common.rb"
 
       @Aliases = {
-        "communication" => lambda { CommunicationDialog() },
-        "security"      => lambda { SecurityDialog() },
-        "csync2"        => lambda { Csync2Dialog() },
-        "conntrack"     => lambda { ConntrackDialog() },
-        "service"       => lambda { ServiceDialog() }
+        "communication"    => lambda { CommunicationDialog() },
+        "corosyncqdevice"  => lambda { CorosyncQdeviceDialog() },
+        "security"         => lambda { SecurityDialog() },
+        "csync2"           => lambda { Csync2Dialog() },
+        "conntrack"        => lambda { ConntrackDialog() },
+        "service"          => lambda { ServiceDialog() }
       }
     end
 
@@ -101,13 +102,18 @@
       sequence = {
         "ws_start"      => "communication",
         "communication" => {
+          :next  => "corosyncqdevice",
+          :back  => "communication",
+          :abort => :abort
+        },
+        "corosyncqdevice" => {
           :next  => "security",
           :back  => "communication",
           :abort => :abort
         },
         "security"      => {
           :next  => "csync2",
-          :back  => "communication",
+          :back  => "corosyncqdevice",
           :abort => :abort
         },
         "csync2"        => {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/src/modules/Cluster.rb 
new/yast2-cluster-4.0.2/src/modules/Cluster.rb
--- old/yast2-cluster-4.0.1/src/modules/Cluster.rb      2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/src/modules/Cluster.rb      2018-01-05 
10:40:16.000000000 +0100
@@ -98,6 +98,17 @@
       # {:addr1=>"10.16.35.105",:nodeid=>"5" }]
       @memberaddr = []
 
+      @corosync_qdevice = false
+
+      @qdevice_model = "net"
+      @qdevice_votes = ""
+
+      @qdevice_host = ""
+      @qdevice_port = "5403"
+      @qdevice_tls = "off"
+      @qdevice_algorithm = "ffsplit"
+      @qdevice_tie_breaker = "lowest"
+
       @csync2_host = []
       @csync2_include = []
     end
@@ -153,6 +164,25 @@
       nil
     end
 
+    def LoadCorosyncQdeviceConfig
+      if SCR.Read(path(".openais.quorum.device"))
+        @corosync_qdevice = true
+      end
+
+      if @corosync_qdevice
+        @qdevice_model = SCR.Read(path(".openais.quorum.device.model"))
+        @qdevice_votes = SCR.Read(path(".openais.quorum.device.votes")).to_s
+
+        @qdevice_host = SCR.Read(path(".openais.quorum.device.net.host"))
+        @qdevice_port = SCR.Read(path(".openais.quorum.device.net.port")).to_s
+        @qdevice_tls = SCR.Read(path(".openais.quorum.device.net.tls"))
+        @qdevice_algorithm = 
SCR.Read(path(".openais.quorum.device.net.algorithm"))
+        @qdevice_tie_breaker = 
SCR.Read(path(".openais.quorum.device.net.tie_breaker"))
+      end
+
+      nil
+    end
+
     def LoadClusterConfig
 
       if Convert.to_string(SCR.Read(path(".openais.totem.secauth"))) == "on"
@@ -245,6 +275,8 @@
         @rrpmode = "passive" if @rrpmode != "passive" && @rrpmode != "active"
       end
 
+      LoadCorosyncQdeviceConfig()
+
       nil
     end
 
@@ -266,6 +298,19 @@
       return address_string
     end
 
+    def SaveCorosyncQdeviceConfig
+      SCR.Write(path(".openais.quorum.device.model"), @qdevice_model)
+      SCR.Write(path(".openais.quorum.device.votes"), @qdevice_votes)
+
+      SCR.Write(path(".openais.quorum.device.net.host"), @qdevice_host)
+      SCR.Write(path(".openais.quorum.device.net.port"), @qdevice_port)
+      SCR.Write(path(".openais.quorum.device.net.tls"), @qdevice_tls)
+      SCR.Write(path(".openais.quorum.device.net.algorithm"), 
@qdevice_algorithm)
+      SCR.Write(path(".openais.quorum.device.net.tie_breaker"), 
@qdevice_tie_breaker)
+
+      nil
+    end
+
     def SaveClusterConfig
 
       if @secauth == true
@@ -277,7 +322,9 @@
       SCR.Write(path(".openais.totem.transport"), @transport)
       SCR.Write(path(".openais.totem.cluster_name"), @cluster_name)
       SCR.Write(path(".openais.totem.ip_version"), @ip_version)
-      SCR.Write(path(".openais.quorum.expected_votes"), @expected_votes)
+      if @expected_votes != ""
+        SCR.Write(path(".openais.quorum.expected_votes"), @expected_votes)
+      end
   
       # BNC#871970, only write member address when interface0  
       if @transport == "udpu"
@@ -339,6 +386,13 @@
         SCR.Write(path(".openais.totem.autoid"), "no")
       end
       SCR.Write(path(".openais.totem.rrpmode"), @rrpmode)
+
+      if @corosync_qdevice
+        SaveCorosyncQdeviceConfig()
+      else
+        SCR.Write(path(".openais.quorum.device"), "")
+      end
+
       SCR.Write(path(".openais"), "")
 
       nil
@@ -543,13 +597,21 @@
         udp_ports = Builtins.add(udp_ports, @mcastport2)
       end
 
-      temp_tcp_ports = ["21064", "7630"]
-      tcp_ports = SuSEFirewallServices.GetNeededTCPPorts("service:cluster")
-      tcp_ports = Convert.convert(
-        Builtins.union(tcp_ports, temp_tcp_ports),
-        :from => "list",
-        :to   => "list <string>"
-      )
+      # 30865 for csync2
+      # 5560 for mgmtd
+      # 7630 for hawk or hawk2
+      # 21064 for dlm
+      # 5403 for corosync qdevice(default)
+      tcp_ports = ["30865", "5560", "21064", "7630"]
+      if @corosync_qdevice
+        tcp_ports.push(@qdevice_port)
+      end
+      #tcp_ports = SuSEFirewallServices.GetNeededTCPPorts("service:cluster")
+      #tcp_ports = Convert.convert(
+      #  Builtins.union(tcp_ports, temp_tcp_ports),
+      #  :from => "list",
+      #  :to   => "list <string>"
+      #)
 
       SuSEFirewallServices.SetNeededPortsAndProtocols(
         "service:cluster",
@@ -740,6 +802,7 @@
     publish :function => :SetProposalValid, :type => "void (boolean)"
     publish :function => :WriteOnly, :type => "boolean ()"
     publish :function => :LoadClusterConfig, :type => "boolean ()"
+    publish :function => :LoadCorosyncQdeviceConfig, :type => "boolean ()"
     publish :function => :SetWriteOnly, :type => "void (boolean)"
     publish :function => :SetAbortFunction, :type => "void (boolean ())"
     publish :variable => :secauth, :type => "boolean"
@@ -748,6 +811,14 @@
     publish :variable => :cluster_name, :type => "string"
     publish :variable => :ip_version, :type => "string"
     publish :variable => :expected_votes, :type => "string"
+    publish :variable => :corosync_qdevice, :type => "boolean"
+    publish :variable => :qdevice_model, :type => "string"
+    publish :variable => :qdevice_votes, :type => "string"
+    publish :variable => :qdevice_host, :type => "string"
+    publish :variable => :qdevice_port, :type => "string"
+    publish :variable => :qdevice_tls, :type => "string"
+    publish :variable => :qdevice_algorithm, :type => "string"
+    publish :variable => :qdevice_tie_breaker, :type => "string"
     publish :variable => :two_node, :type => "string"
     publish :variable => :config_format, :type => "string"
     publish :variable => :mcastport1, :type => "string"
@@ -765,6 +836,7 @@
     publish :variable => :transport, :type => "string"
     publish :variable => :memberaddr, :type => "list <string>"
     publish :function => :SaveClusterConfig, :type => "void ()"
+    publish :function => :SaveCorosyncQdeviceConfig, :type => "void ()"
     publish :variable => :csync2_host, :type => "list <string>"
     publish :variable => :csync2_include, :type => "list <string>"
     publish :function => :load_csync2_conf, :type => "void ()"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-cluster-4.0.1/src/servers_non_y2/ag_openais 
new/yast2-cluster-4.0.2/src/servers_non_y2/ag_openais
--- old/yast2-cluster-4.0.1/src/servers_non_y2/ag_openais       2017-12-04 
10:34:09.000000000 +0100
+++ new/yast2-cluster-4.0.2/src/servers_non_y2/ag_openais       2018-01-05 
10:40:16.000000000 +0100
@@ -5,7 +5,7 @@
 # Authors:     Xinwei Hu <x...@suse.de>
 #              Lukas Ocilka <loci...@suse.cz>
 #
-# File:                ag_multipath
+# File:                ag_openais
 #
 # License:
 #
@@ -36,6 +36,7 @@
 import copy
 import sys
 
+debug = False
 
 #the option table is used to parse and write suggested_value if no options are 
read
 
@@ -165,14 +166,33 @@
                "allow_downscale":{"doc":"Enables allow downscale (AD) 
feature","type":"int", "default_value":0},
                }
 
+quorum_qdevice_option_table = {
+               "model":{"doc":"Specifies the model to be used, currently only 
(net) is 
supported.","type":"string","default_value":"net","suggested_value":"net"},
+               "timeout":{"doc":"Specifies how often corosync-qdevice should 
call the votequorum_poll 
function.","type":"int","default_value":10000,"suggested_value":10000},
+               "sync_timeout":{"doc":"Specifies how often corosync-qdevice 
should call the votequorum_poll function during a sync 
phase.","type":"int","default_value":30000,"suggested_value":30000},
+               "votes":{"doc":"The number of votes provided to the cluster by 
qdevice. Default is sum(votes_per_node) - 1.","type":"int","default_value":0}
+               }
+
+quorum_qdevice_net_option_table = {
+               "host":{"doc":"Specifies the IP address or host name of the 
qnetd server to be used, is required.","type":"string","default_value":"0"},
+               "port":{"doc":"Specifies TCP port of qnetd 
server.","default_value":5403, "type":"int","suggested_value":5403},
+               "tls":{"doc":"Specifies if tls should be used, only off is 
supported by yast.","default_value":"off", 
"type":"string","suggested_value":"off"},
+               "algorithm":{"doc":"Decision algorithm. Can be one of the 
ffsplit or lms.","default_value":"ffsplit", 
"type":"string","suggested_value":"ffsplit"},
+               "tie_breaker":{"doc":"Used as a fallback if qdevice has to 
decide between two or more equal partitions.","default_value":"lowest", 
"type":"string","suggested_value":"lowest"},
+               "connect_timeout":{"doc":"Timeout when corosync-qdevice is 
trying to connect to corosync-qnetd host. Default is (0.8 * 
quorum.sync_timeout).","type":"int","default_value":0},
+               "force_ip_version":{"doc":"Can be one of 0|4|6 and forces the 
software to use the given IP version.", "default_value":0, 
"type":"int","suggested_value":0}
+               }
+
 qb_option_table = { "ipc_type":{"doc":"This specifies type of IPC to use",
                    "type":"string", "default_value":"native"}
                  }
 
-totem_options = {"interface":[]}
-logging_options = {"logger_subsys":[]}
+# Using [] instead of {} is because some sections like nodelist have many nodes
+totem_options = {}
+logging_options = {}
+quorum_qdevice_options={}
 quorum_options={}
-nodelist_options={"node":[]}
+nodelist_options={}
 qb_options={}
 
 def strip_comments_and_pending_space(line):
@@ -183,7 +203,7 @@
        return strip_comments_and_pending_space(l)
 
 
-def fulfill_default_logging_options ():
+def fulfill_suggested_logging_options ():
        for opt in logging_option_table.keys():
                if opt == "logger": continue
                sv = logging_option_table[opt].get("suggested_value", None)
@@ -208,11 +228,46 @@
                if v == None and sv != None:
                        quorum_options[opt] = sv
 
+def fulfill_suggested_quorum_qdevice_options():
+       '''
+               This function only be used when having quorum qdevice
+       '''
+       # must have 'model' in 'device'
+       for opt in ['model']:
+               sv = quorum_qdevice_option_table[opt].get("suggested_value", 
None)
+               v = quorum_qdevice_options.get(opt, None)
+               if v == None and sv != None:
+                       quorum_qdevice_options[opt] = sv
+
+       # must have 'host', 'tls', 'algorithm', 'tie_breaker' in 'net'
+       for opt in ['host', 'tls', 'algorithm', 'tie_breaker']:
+               sv = 
quorum_qdevice_net_option_table[opt].get("suggested_value", None)
+               v = quorum_qdevice_options["net"].get(opt, None)
+               if v == None and sv != None:
+                       quorum_qdevice_options["net"][opt] = sv
+
+def print_quorum_qdevice_net_options(f, indent):
+       '''
+               indent means the level of sub-directive
+       '''
+       f.write("\t"*indent+"net {\n")
+       for l in quorum_qdevice_options["net"].keys():
+               f.write("\t"*(indent+1)+"#%s\n" % 
(quorum_qdevice_net_option_table[l]["doc"]))
+               f.write("\t"*(indent+1)+"%s:\t%s\n\n" % (l, 
quorum_qdevice_options["net"][l]))
+       f.write("\t"*indent+"}\n")
+
 def print_quorum_options(f):
-       f.write("quorum {\n");
+       f.write("quorum {\n")
        for key in quorum_options.keys():
-               if quorum_options[key] == "":
-                       continue
+               if key == "device" and is_quorum_qdevice_configured():
+                       f.write("\tdevice {\n")
+                       for l in quorum_options["device"].keys():
+                               if l == "net":
+                                       print_quorum_qdevice_net_options(f, 2)
+                               else:
+                                       f.write("\t\t#%s\n" % 
(quorum_qdevice_option_table[l]["doc"]))
+                                       f.write("\t\t%s:\t%s\n\n" % (l, 
quorum_qdevice_options[l]))
+                       f.write("\t}\n")
                else:
                        f.write("\t#%s\n" % (quorum_option_table[key]["doc"]))
                        f.write("\t%s:\t%s\n\n" % (key, quorum_options[key]))
@@ -221,7 +276,7 @@
 def print_qb_options(f):
        if(len(qb_options) == 0):
                return
-       f.write("qb {\n");
+       f.write("qb {\n")
        for key in qb_options.keys():
                f.write("\t#%s\n" % (qb_option_table[key]["doc"]))
                f.write("\t%s:\t%s\n\n" % (key, qb_options[key]))
@@ -231,7 +286,7 @@
        nodelist = nodelist_options["node"]
        if len(nodelist) ==  0:
                return
-       f.write("nodelist {\n");
+       f.write("nodelist {\n")
        for node in nodelist:
                f.write("\tnode {\n")
                # Sort the keys like ring0 and ring1
@@ -245,6 +300,11 @@
                f.write("\t}\n")
        f.write("}\n")
 
+def is_quorum_qdevice_configured():
+       if ("device" not in quorum_options) or (len(quorum_qdevice_options) == 
0):
+               return False
+       else:
+               return True
                        
 def print_logging_options(f):
        f.write("logging {\n")
@@ -256,22 +316,20 @@
                                        f.write("\t\t#%s\n\n" % 
(logger_subsys_option_table[l]["doc"]))
                                        f.write("\t\t%s:\t%s\n\n" % (l, log[l]))
                                f.write("\t}\n")
-                       continue
-               f.write("\t#%s\n" % (logging_option_table[key]["doc"]))
-               f.write("\t%s:\t%s\n\n" % (key, logging_options[key]))
+               else:
+                       f.write("\t#%s\n" % (logging_option_table[key]["doc"]))
+                       f.write("\t%s:\t%s\n\n" % (key, logging_options[key]))
        f.write("}\n")
 
 def print_totem_options(f):
-
        f.write("totem {\n")
-       transport_protocol = totem_options.get("transport", "udp")
        for key in totem_options.keys():
                if key == "interface":
                        for inf in totem_options["interface"]:
                                f.write("\tinterface {\n")
                                for k in inf.keys():
                                        if inf[k] == "" or k == "oldlist":
-                                               continue;
+                                               continue
                                        else:
                                                f.write("\t\t#%s\n" % 
(interface_option_table[k]["doc"]))
                                                f.write("\t\t%s:\t%s\n\n" % (k, 
inf[k]))
@@ -298,7 +356,7 @@
        global nodelist_options
        global qb_options
        global quorum_options
-       
+
        for l in file:
                i = strip_comments_and_pending_space(l)
                if i == "":
@@ -307,19 +365,29 @@
                if i[-1] == "{":
                        i = i[:-1].rsplit()[0]
                        if i == "totem":
-                               totem_options = opt_parser(file, 
totem_option_table)
+                               totem_options = opt_parser(file, 
totem_option_table, "totem")
                        elif i == "logging":
-                               logging_options = opt_parser(file, 
logging_option_table)
+                               logging_options = opt_parser(file, 
logging_option_table, "logging")
                        elif i == "nodelist":
-                               nodelist_options = opt_parser(file, {})
+                               nodelist_options = opt_parser(file, {}, 
"nodelist")
                        elif i == "quorum":
-                               quorum_options = opt_parser(file, 
quorum_option_table)
+                               quorum_options = opt_parser(file, 
quorum_option_table, "quorum")
                        elif i == "qb":
-                               qb_options = opt_parser(file, qb_option_table)
+                               qb_options = opt_parser(file, qb_option_table, 
"qb")
                        else:
                                pass
+
+       if debug:
+               print("Reading from /etc/corosync/corosync.conf:")
+               print(totem_options)
+               print(logging_options)
+               print(nodelist_options)
+               print(qb_options)
+               print(quorum_options)
        
-def opt_parser(file, options):
+def opt_parser(file, options, parent):
+       global quorum_qdevice_options
+
        result = {}
        i = ""
        while (i == ""):
@@ -327,21 +395,29 @@
 
        while (i[-1] != "}"):
                if (i[-1] == "{"):
-                       if i.lstrip().split(" ")[0] == "interface":
+                       if i.lstrip().split(" ")[0] == "interface" and parent 
== "totem":
                                infs = result.get("interface", [])
-                               infs.append(opt_parser(file, 
interface_option_table))
+                               infs.append(opt_parser(file, 
interface_option_table, "interface"))
                                result["interface"] = infs
-                       elif i.lstrip().split(" ")[0] == "logger_subsys":
+                       elif i.lstrip().split(" ")[0] == "logger_subsys" and 
parent == "logging":
                                logs = result.get("logger_subsys", [])
-                               logs.append(opt_parser(file, 
logger_subsys_option_table))
+                               logs.append(opt_parser(file, 
logger_subsys_option_table, "logger_subsys"))
                                result["logger_subsys"] = logs
-                       elif i.lstrip().split(" ")[0] == "node":
+                       elif i.lstrip().split(" ")[0] == "node" and parent == 
"nodelist":
                                members = result.get("node", [])
-                               members.append(opt_parser(file, 
node_option_table))
+                               members.append(opt_parser(file, 
node_option_table, "node"))
                                result["node"] = members
-                       elif i.lstrip().split(" ")[0] == "member":
+                       elif i.lstrip().split(" ")[0] == "device" and parent == 
"quorum":
+                               dev = result.get("device", {})
+                               quorum_qdevice_options = opt_parser(file, 
quorum_qdevice_option_table, "qdevice")
+                               result["device"] = quorum_qdevice_options
+                       elif i.lstrip().split(" ")[0] == "net" and parent == 
"qdevice":
+                               net = result.get("net", {})
+                               result["net"] = opt_parser(file, 
quorum_qdevice_net_option_table, "net")
+                       # member of interface is removed in the latest version
+                       elif i.lstrip().split(" ")[0] == "member" and parent == 
"interface":
                                oldmembers = result.get("oldlist", [])
-                               oldmembers.append(opt_parser(file, 
old_memberlist_table))
+                               oldmembers.append(opt_parser(file, 
old_memberlist_table, "oldlist"))
                                result["oldlist"] = oldmembers
                        else:
                                #y2warning("Unknown sub-directive %s found. 
Ignore it" % (i.lstrip().split(" ")[0]))
@@ -416,7 +492,7 @@
 def generateMemberString():
        member_str = ""
 
-       for item in  nodelist_options.get("node"):
+       for item in nodelist_options.get("node"):
                if "ring0_addr" in item:
                        address1 = item.get("ring0_addr", None)
                        member_str += address1
@@ -468,8 +544,10 @@
        def doList(self, path):
                #remove the leading dot
                path_arr = path
+               if len(path_arr) == 0:
+                       return '["allconfs"]'
        
-               if path_arr[0] == '':
+               if path_arr[0] == 'allconfs' and len(path_arr) == 1:
                        return '["quorum", "totem", "nodelist"]'
                elif path_arr[0] == 'totem':
                        if len(path_arr) == 1:
@@ -499,6 +577,15 @@
                elif path_arr[0] == 'nodelist':
                        if len(path_arr) == 1:
                                return '["node"]'
+               elif path_arr[0] == 'quorum':
+                       if len(path_arr) == 1:
+                               return '["provider", "device", 
"expected_votes", "two_node"]'
+                       elif len(path_arr) == 2 and path_arr[1] == "device":
+                               return '["model", "timeout", "sync_timeout", 
"net", "votes"]'
+                       elif len(path_arr) == 3 and path_arr[2] == "net":
+                               return '["host", "port", "tls", "algorithm", 
"tie_breaker", "connect_timeout", "force_ip_version"]'
+                       else:
+                               return '[]'
                else:
                        return 'nil\n'
        
@@ -506,12 +593,25 @@
                if path[0] == "":
                        return "nil\n"
                elif path[0] == "quorum" and len(path) >= 2:
-                       if path[1] == "expected_votes":
-                               return '"%s"' % 
quorum_options.get("expected_votes", "")
-                       if path[1] == "two_node":
-                               return '"%s"' % quorum_options.get("two_node", 
"")
+                       if len(path) == 2 and path[1] in 
quorum_option_table.keys():
+                               return '"%s"' % quorum_options.get(path[1],
+                                       
quorum_option_table[path[1]]["default_value"])
+                       elif len(path) == 2 and path[1] == "device":
+                               if is_quorum_qdevice_configured():
+                                       return "true"
+                               else:
+                                       return "nil"
+                       elif is_quorum_qdevice_configured() and len(path) == 3 
and \
+                               path[1] == "device" and path[2] in 
quorum_qdevice_option_table.keys():
+                               return '"%s"' % 
quorum_options["device"].get(path[2],
+                                       
quorum_qdevice_option_table[path[2]]["default_value"])
+                       elif is_quorum_qdevice_configured() and len(path) == 4 
and \
+                               path[1] == "device" and path[2] == "net" and \
+                               path[3] in 
quorum_qdevice_net_option_table.keys():
+                               return '"%s"' % 
quorum_qdevice_options["net"].get(path[3],
+                                       
quorum_qdevice_net_option_table[path[3]]["default_value"])
                        else:
-                               return ""
+                               return "nil"
                elif path[0] == "totem":
                        if len(path) == 1:
                                return "nil"
@@ -588,9 +688,12 @@
 
        def saveFile(self):
 
-               fulfill_default_logging_options()
+        # Only fulfill the must option
+               fulfill_suggested_logging_options()
                fulfill_suggested_totem_options()
                fulfill_suggested_quorum_options()
+               if is_quorum_qdevice_configured():
+                       fulfill_suggested_quorum_qdevice_options()
 
                f = open("/etc/corosync/corosync.conf.YaST2", "w")
                f.write("# /etc/corosync/corosync.conf file autogenerated by 
YaST2.\n")
@@ -613,15 +716,32 @@
                pass
 
        def doWrite(self, path, args):
+               global quorum_options
+               global quorum_qdevice_options
+
                if path[0] == "":
                        self.saveFile()
                        return "true"
                elif path[0] == "quorum":
-                       if path[1] == "expected_votes":
-                               quorum_options["expected_votes"] = args
+                       if len(path) == 2 and path[1] in 
quorum_option_table.keys():
+                               quorum_options[path[1]] = args
+                               return "true"
+                       elif len(path) == 2 and path[1] == "device" and args == 
"":
+                               del(quorum_options["device"])
+                               quorum_qdevice_options = {}
+                               return "true"
+                       elif len(path) == 3 and path[1] == "device" and \
+                               path[2] in quorum_qdevice_option_table.keys():
+                               if "device" not in quorum_options:
+                                       quorum_options["device"] = 
quorum_qdevice_options
+                               quorum_qdevice_options[path[2]] = args
                                return "true"
-                       elif path[1] == "two_node":
-                               quorum_options["two_node"] = args
+                       elif len(path) == 4 and path[1] == "device" and \
+                               path[2] == "net" and path[3] in 
quorum_qdevice_net_option_table.keys():
+                               if "net" not in quorum_qdevice_options:
+                                       quorum_qdevice_options["net"] = {}
+                                       quorum_options["device"] = 
quorum_qdevice_options
+                               quorum_qdevice_options["net"][path[3]] = args
                                return "true"
                        else:
                                return "false"
@@ -750,10 +870,10 @@
                self.args = ""
                self.path = ""
                        
-               #y2debug ("waiting for a command");
+               #y2debug ("waiting for a command")
                scr_command = sys.stdin.readline().strip()
                
-               #y2debug ("newline: %s" % scr_command);
+               #y2debug ("newline: %s" % scr_command)
                
                # eg. Read(.totem.interface.interface0.binnetaddr,"args")  
Write(.)
                p = re.compile('^`?(\w+)\s*(\(([^,]*)(,\s*(.*))?\s*\))?\s*$')
@@ -798,7 +918,10 @@
        while True:
                scr_agent.SCR_Command ()
 
-               #y2debug("Command %s %s" % (scr_agent.command,scr_agent.path));
+               #y2debug("Command %s %s: %s" % 
(scr_agent.command,scr_agent.path,scr_agent.args))
+               if debug:
+                       print("Command %s %s: %s" % (scr_agent.command,
+                               scr_agent.path,scr_agent.args))
                
                if (scr_agent.command == 'Dir' ):
                        print(openais_agent.doList(scr_agent.path))


Reply via email to