Skip to content

Commit

Permalink
corosync.conf parser fixes
Browse files Browse the repository at this point in the history
* pcs: del_attribute does not raise an exception when deleting
  a non-existing attribute any more (consistent with pcsd)
* pcs: fixed del_attributes_by name and set_attribute
* pcs: fixed reading a cluster name from corosync.conf
* pcsd: added full-featured corosync.conf parser
* pcsd: fixed reading a cluster name from corosync.conf
  • Loading branch information
tomjelinek committed May 21, 2015
1 parent 367fc74 commit b4e73ec
Show file tree
Hide file tree
Showing 7 changed files with 1,417 additions and 36 deletions.
30 changes: 20 additions & 10 deletions pcs/corosync_conf.py
Expand Up @@ -52,22 +52,30 @@ def add_attribute(self, name, value):
return self

def del_attribute(self, attribute):
self._attr_list.remove(attribute)
self._attr_list = [
attr for attr in self._attr_list if attr != attribute
]
return self

def del_attributes_by_name(self, name, value=None):
for index, attr in enumerate(self._attr_list):
if attr[0] == name and (value is None or attr[1] == value):
del self._attr_list[index]
self._attr_list = [
attr for attr in self._attr_list
if not(attr[0] == name and (value is None or attr[1] == value))
]
return self

def set_attribute(self, name, value):
existing_attrs = self.get_attributes(name)
if existing_attrs:
existing_attrs[0][1] = value
for attr in existing_attrs[1:]:
self.del_attribute(attr)
else:
found = False
new_attr_list = []
for attr in self._attr_list:
if attr[0] != name:
new_attr_list.append(attr)
elif not found:
found = True
attr[1] = value
new_attr_list.append(attr)
self._attr_list = new_attr_list
if not found:
self.add_attribute(name, value)
return self

Expand All @@ -91,6 +99,8 @@ def add_section(self, section):

def del_section(self, section):
self._section_list.remove(section)
# don't set parent to None if the section was not found in the list
# thanks to remove raising a ValueError in that case
section._parent = None
return self

Expand Down
24 changes: 18 additions & 6 deletions pcs/test/test_corosync_conf.py
Expand Up @@ -147,6 +147,18 @@ def test_attribute_set(self):
]
)

section.add_attribute("name1", "value1")
section.add_attribute("name1", "value1")
section.set_attribute("name1", "value1")
self.assertEquals(
section.get_attributes(),
[
["name1", "value1"],
["name2", "value2a"],
["name3", "value3"],
]
)

def test_attribute_change(self):
section = corosync_conf.Section("mySection")
section.add_attribute("name1", "value1")
Expand Down Expand Up @@ -180,7 +192,6 @@ def test_attribute_del(self):
[
["name1", "value1"],
["name3", "value3"],
["name2", "value2"],
]
)

Expand All @@ -189,13 +200,15 @@ def test_attribute_del(self):
section.get_attributes(),
[
["name1", "value1"],
["name2", "value2"],
]
)

self.assertRaises(
ValueError,
section.del_attribute, ["name3", "value3"]
section.del_attribute(["name3", "value3"])
self.assertEquals(
section.get_attributes(),
[
["name1", "value1"],
]
)

def test_attribute_del_by_name(self):
Expand Down Expand Up @@ -256,7 +269,6 @@ def test_attribute_del_by_name(self):
["name3", "value3a"],
]
)

section.del_attributes_by_name("name3")
self.assertEquals(
section.get_attributes(),
Expand Down
6 changes: 5 additions & 1 deletion pcs/utils.py
Expand Up @@ -1655,9 +1655,13 @@ def getClusterName():
f = open(settings.corosync_conf_file,'r')
conf = corosync_conf_utils.parse_string(f.read())
f.close()
# mimic corosync behavior - the last cluster_name found is used
cluster_name = None
for totem in conf.get_sections("totem"):
for attrs in totem.get_attributes("cluster_name"):
return attrs[1]
cluster_name = attrs[1]
if cluster_name:
return cluster_name
except (IOError, corosync_conf_utils.CorosyncConfException) as e:
return ""

Expand Down
152 changes: 152 additions & 0 deletions pcsd/corosyncconf.rb
@@ -0,0 +1,152 @@
module CorosyncConf
class Section
attr_reader :parent, :name

def initialize(name)
@parent = nil
@attr_list = []
@section_list = []
@name = name
end

def text(indent=' ')
lines = []
@attr_list.each { |attrib|
lines << "#{attrib[0]}: #{attrib[1]}"
}
lines << '' if not(@attr_list.empty? or @section_list.empty?)
last_section = @section_list.length - 1
@section_list.each_with_index { |section, index|
lines += section.text.split("\n")
lines.pop if lines[-1].strip.empty?
lines << '' if index < last_section
}
if @parent
lines.map! { |item| item.empty? ? item : indent + item }
lines.unshift("#{@name} {")
lines << '}'
end
final = lines.join("\n")
final << "\n" if not final.empty?
return final
end

def root
parent = self
parent = parent.parent while parent.parent
return parent
end

def attributes(name=nil)
return @attr_list.find_all { |attrib| not name or attrib[0] == name }
end

def add_attribute(name, value)
@attr_list << [name, value]
return self
end

def del_attribute(attribute)
@attr_list.delete(attribute)
return self
end

def del_attributes_by_name(name, value=nil)
@attr_list.reject! { |attrib|
attrib[0] == name and (not value or attrib[1] == value)
}
return self
end

def set_attribute(name, value)
found = false
new_attr_list = []
@attr_list.each { |attrib|
if attrib[0] != name
new_attr_list << attrib
elsif not found
found = true
attrib[1] = value
new_attr_list << attrib
end
}
@attr_list = new_attr_list
self.add_attribute(name, value) if not found
return self
end

def sections(name=nil)
return @section_list.find_all { |section|
not name or section.name == name
}
end

def add_section(section)
parent = self
while parent
raise CircularParentshipException if parent == section
parent = parent.parent
end
section.parent.del_section(section) if section.parent
section.parent = self
@section_list << section
return self
end

def del_section(section)
if @section_list.delete(section)
# don't set parent to nil if the section was not found in the list
section.parent = nil
end
return self
end

protected

def parent=(parent)
@parent = parent
return self
end
end


def CorosyncConf::parse_string(conf_text)
root = Section.new('')
self.parse_section(conf_text.split("\n"), root)
return root
end

def CorosyncConf::parse_section(lines, section)
# parser is trying to work the same way as an original corosync parser
while not lines.empty?
current_line = lines.shift().strip()
next if current_line.empty? or current_line.start_with?('#')
if current_line.include?('{')
section_name = current_line.rpartition('{').first
new_section = Section.new(section_name.strip)
section.add_section(new_section)
self.parse_section(lines, new_section)
elsif current_line.include?('}')
if not section.parent
raise ParseErrorException, 'Unexpected closing brace'
end
return
elsif current_line.include?(':')
section.add_attribute(
*current_line.split(':', 2).map { |part| part.strip }
)
end
end
raise ParseErrorException, 'Missing closing brace' if section.parent
end


class CorosyncConfException < Exception
end

class CircularParentshipException < CorosyncConfException
end

class ParseErrorException < CorosyncConfException
end
end
32 changes: 13 additions & 19 deletions pcsd/pcs.rb
Expand Up @@ -10,6 +10,7 @@

require 'config.rb'
require 'cfgsync.rb'
require 'corosyncconf.rb'

def getAllSettings()
stdout, stderr, retval = run_cmd(PCS, "property")
Expand Down Expand Up @@ -475,27 +476,20 @@ def get_cluster_name()
# Cluster probably isn't running, try to get cluster name from
# corosync.conf
begin
corosync_conf = Cfgsync::CorosyncConf.from_file().text()
corosync_conf = CorosyncConf::parse_string(
Cfgsync::CorosyncConf.from_file().text()
)
# mimic corosync behavior - the last cluster_name found is used
cluster_name = nil
corosync_conf.sections('totem').each { |totem|
totem.attributes('cluster_name').each { |attrib|
cluster_name = attrib[1]
}
}
return cluster_name if cluster_name
rescue
return ""
end
in_totem = false
current_level = 0
corosync_conf.each_line do |line|
if line =~ /totem\s*\{/
in_totem = true
end
if in_totem
md = /cluster_name:\s*(\w+)/.match(line)
if md
return md[1]
end
end
if in_totem and line =~ /\}/
in_totem = false
end
return ''
end

return ""
else
return stdout.join().gsub(/.*= /,"").strip
Expand Down
1 change: 1 addition & 0 deletions pcsd/test/test_all_suite.rb
@@ -1,3 +1,4 @@
require 'test/unit'
require 'test_cfgsync.rb'
require 'test_config.rb'
require 'test_corosyncconf.rb'

0 comments on commit b4e73ec

Please sign in to comment.