occi-core 4.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +43 -0
  5. data/.yardopts +1 -0
  6. data/AUTHORS +9 -0
  7. data/Gemfile +11 -0
  8. data/LICENSE +13 -0
  9. data/README.md +212 -0
  10. data/Rakefile +28 -0
  11. data/config/occi.yml +4 -0
  12. data/ext/mkrf_conf.rb +35 -0
  13. data/lib/occi/collection.rb +192 -0
  14. data/lib/occi/core/action.rb +32 -0
  15. data/lib/occi/core/action_instance.rb +38 -0
  16. data/lib/occi/core/actions.rb +20 -0
  17. data/lib/occi/core/attribute.rb +99 -0
  18. data/lib/occi/core/attributes.rb +172 -0
  19. data/lib/occi/core/categories.rb +51 -0
  20. data/lib/occi/core/category.rb +153 -0
  21. data/lib/occi/core/entities.rb +47 -0
  22. data/lib/occi/core/entity.rb +264 -0
  23. data/lib/occi/core/kind.rb +58 -0
  24. data/lib/occi/core/kinds.rb +22 -0
  25. data/lib/occi/core/link.rb +95 -0
  26. data/lib/occi/core/links.rb +34 -0
  27. data/lib/occi/core/mixin.rb +55 -0
  28. data/lib/occi/core/mixins.rb +41 -0
  29. data/lib/occi/core/properties.rb +35 -0
  30. data/lib/occi/core/related.rb +7 -0
  31. data/lib/occi/core/resource.rb +78 -0
  32. data/lib/occi/core/resources.rb +14 -0
  33. data/lib/occi/core.rb +31 -0
  34. data/lib/occi/helpers/inspect.rb +12 -0
  35. data/lib/occi/infrastructure/compute.rb +122 -0
  36. data/lib/occi/infrastructure/network/ipnetwork.rb +27 -0
  37. data/lib/occi/infrastructure/network.rb +107 -0
  38. data/lib/occi/infrastructure/networkinterface/ipnetworkinterface.rb +27 -0
  39. data/lib/occi/infrastructure/networkinterface.rb +103 -0
  40. data/lib/occi/infrastructure/os_tpl.rb +19 -0
  41. data/lib/occi/infrastructure/resource_tpl.rb +19 -0
  42. data/lib/occi/infrastructure/storage.rb +61 -0
  43. data/lib/occi/infrastructure/storagelink.rb +56 -0
  44. data/lib/occi/infrastructure.rb +25 -0
  45. data/lib/occi/log.rb +66 -0
  46. data/lib/occi/model.rb +86 -0
  47. data/lib/occi/parser/json.rb +34 -0
  48. data/lib/occi/parser/ova.rb +35 -0
  49. data/lib/occi/parser/ovf.rb +154 -0
  50. data/lib/occi/parser/text.rb +234 -0
  51. data/lib/occi/parser/xml.rb +18 -0
  52. data/lib/occi/parser.rb +78 -0
  53. data/lib/occi/settings.rb +9 -0
  54. data/lib/occi/version.rb +3 -0
  55. data/lib/occi-core.rb +50 -0
  56. data/occi-core.gemspec +36 -0
  57. data/spec/occi/collection_spec.rb +38 -0
  58. data/spec/occi/core/attribute_spec.rb +0 -0
  59. data/spec/occi/core/attributes_spec.rb +42 -0
  60. data/spec/occi/core/categories_spec.rb +27 -0
  61. data/spec/occi/core/category_spec.rb +38 -0
  62. data/spec/occi/core/entity_spec.rb +46 -0
  63. data/spec/occi/core/resource_spec.rb +18 -0
  64. data/spec/occi/infrastructure/compute_spec.rb +29 -0
  65. data/spec/occi/log_spec.rb +14 -0
  66. data/spec/occi/model_spec.rb +50 -0
  67. data/spec/occi/parser/text_spec.rb +31 -0
  68. data/spec/occi/parser_spec.rb +114 -0
  69. data/spec/occi/test.json +33 -0
  70. data/spec/occi/test.ova +0 -0
  71. data/spec/occi/test.ovf +198 -0
  72. data/spec/spec_helper.rb +25 -0
  73. metadata +304 -0
data/lib/occi/log.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'logger'
2
+
3
+ module Occi
4
+ class Log
5
+
6
+ include ::Logger::Severity
7
+
8
+ attr_reader :logger
9
+
10
+ # creates a new OCCI logger
11
+ # @param [IO,String] log_dev The log device. This is a filename (String) or IO object (typically +STDOUT+,
12
+ # +STDERR+, or an open file).
13
+ def initialize(log_dev)
14
+ if log_dev.kind_of? Logger
15
+ @logger = log_dev
16
+ else
17
+ @logger = Logger.new(log_dev)
18
+ end
19
+
20
+ # subscribe to log messages and send to logger
21
+ @log_subscriber = ActiveSupport::Notifications.subscribe("log") do |name, start, finish, id, payload|
22
+ @logger.log(payload[:level], payload[:message]) if @logger
23
+ end
24
+ end
25
+
26
+ def close
27
+ ActiveSupport::Notifications.unsubscribe(@log_subscriber)
28
+ end
29
+
30
+ # @param [Logger::Severity] severity
31
+ def level=(severity)
32
+ @logger.level = severity
33
+ end
34
+
35
+ # @return [Logger::Severity]
36
+ def level
37
+ @logger.level
38
+ end
39
+
40
+ # @see info
41
+ def self.debug(message)
42
+ ActiveSupport::Notifications.instrument("log", :level => Logger::DEBUG, :message => message)
43
+ end
44
+
45
+ # Log an +INFO+ message
46
+ # @param [String] message the message to log; does not need to be a String
47
+ def self.info(message)
48
+ ActiveSupport::Notifications.instrument("log", :level => Logger::INFO, :message => message)
49
+ end
50
+
51
+ # @see info
52
+ def self.warn(message)
53
+ ActiveSupport::Notifications.instrument("log", :level => Logger::WARN, :message => message)
54
+ end
55
+
56
+ # @see info
57
+ def self.error(message)
58
+ ActiveSupport::Notifications.instrument("log", :level => Logger::ERROR, :message => message)
59
+ end
60
+
61
+ # @see info
62
+ def self.fatal(message)
63
+ ActiveSupport::Notifications.instrument("log", :level => Logger::FATAL, :message => message)
64
+ end
65
+ end
66
+ end
data/lib/occi/model.rb ADDED
@@ -0,0 +1,86 @@
1
+ module Occi
2
+ class Model < Occi::Collection
3
+
4
+ # @param [Occi::Core::Collection] collection
5
+ def initialize(collection=nil)
6
+ super(nil, nil) # model must be empty for model class
7
+ register_core
8
+ register_collection collection if collection.kind_of? Occi::Collection
9
+ end
10
+
11
+ def model=(model)
12
+ # will not assign a model inside a model
13
+ end
14
+
15
+ # register Occi Core categories enitity, resource and link
16
+ def register_core
17
+ Occi::Log.info "### Registering OCCI Core categories enitity, resource and link ###"
18
+ register Occi::Core::Entity.kind
19
+ register Occi::Core::Resource.kind
20
+ register Occi::Core::Link.kind
21
+ end
22
+
23
+ # register Occi Infrastructure categories
24
+ def register_infrastructure
25
+ Occi::Log.info "### Registering OCCI Infrastructure categories ###"
26
+ Occi::Infrastructure.categories.each { |category| register category }
27
+ end
28
+
29
+ # register OCCI categories from files
30
+ #
31
+ # @param [String] path to a folder containing files which include OCCI collections in JSON format. The path is
32
+ # recursively searched for files with the extension .json .
33
+ # @param [Sting] scheme_base_url base location for provider specific extensions of the OCCI model
34
+ def register_files(path, scheme_base_url='http://localhost')
35
+ Occi::Log.info "### Initializing OCCI Model from #{path} ###"
36
+ Dir.glob(path + '/**/*.json').each do |file|
37
+ collection = Occi::Collection.new(JSON.parse(File.read(file)))
38
+ # add location of service provider to scheme if it has a relative location
39
+ collection.kinds.collect { |kind| kind.scheme = scheme_base_url + kind.scheme if kind.scheme.start_with? '/' } if collection.kinds
40
+ collection.mixins.collect { |mixin| mixin.scheme = scheme_base_url + mixin.scheme if mixin.scheme.start_with? '/' } if collection.mixins
41
+ collection.actions.collect { |action| action.scheme = scheme_base_url + action.scheme if action.scheme.start_with? '/' } if collection.actions
42
+ register_collection collection
43
+ end
44
+ end
45
+
46
+ # register OCCI categories from OCCI collection
47
+ def register_collection(collection)
48
+ collection.kinds.each { |kind| kind.model = self }
49
+ collection.mixins.each { |mixin| mixin.model = self }
50
+ collection.actions.each { |action| action.model = self }
51
+ merge! collection
52
+ end
53
+
54
+ # clear all entities from all categories
55
+ def reset()
56
+ categories.each { |category| category.entities = [] if category.respond_to? :entities }
57
+ end
58
+
59
+ # @param [Occi::Core::Category] category
60
+ def register(category)
61
+ Occi::Log.debug "### Registering category #{category}"
62
+ # add model to category as back reference
63
+ category.model = self
64
+ @kinds << category unless get_by_id(category.to_s) if category.class.ancestors.include? Occi::Core::Kind
65
+ @mixins << category unless get_by_id(category.to_s) if category.class.ancestors.include? Occi::Core::Mixin
66
+ @actions << category unless get_by_id(category.to_s) if category.class.ancestors.include? Occi::Core::Action
67
+ end
68
+
69
+ # @param [Occi::Core::Category] category
70
+ def unregister(category)
71
+ Occi::Log.debug "### Unregistering category #{category.type_identifier}"
72
+ @kinds.delete category
73
+ @mixins.delete category
74
+ @actions.delete category
75
+ end
76
+
77
+ # Return all categories from model. If filter is present, return only the categories specified by filter
78
+ #
79
+ # @param [Occi::Collection,Occi::Core::Category,String] filter
80
+ # @return [Occi::Collection] collection
81
+ def get(filter = nil)
82
+ filter ? self.get_related_to(filter) : self
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,34 @@
1
+ module Occi
2
+ module Parser
3
+ module Json
4
+ # @param [String] string
5
+ # @return [Occi::Collection]
6
+ def self.collection(string)
7
+ collection = Occi::Collection.new
8
+ hash = Hashie::Mash.new(JSON.parse(string))
9
+ collection.kinds.merge hash.kinds.collect { |kind| Occi::Core::Kind.new(kind.scheme, kind.term, kind.title, kind.attributes, kind.related, kind.actions) } if hash.kinds
10
+ collection.mixins.merge hash.mixins.collect { |mixin| Occi::Core::Mixin.new(mixin.scheme, mixin.term, mixin.title, mixin.attributes, mixin.related, mixin.actions) } if hash.mixins
11
+ collection.actions.merge hash.actions.collect { |action| Occi::Core::Action.new(action.scheme, action.term, action.title, action.attributes) } if hash.actions
12
+ collection.resources.merge hash.resources.collect { |resource| Occi::Core::Resource.new(resource.kind, resource.mixins, resource.attributes, resource.actions, resource.links) } if hash.resources
13
+ collection.links.merge hash.links.collect { |link| Occi::Core::Link.new(link.kind, link.mixins, link.attributes, [], nil, link.target) } if hash.links
14
+
15
+ if collection.resources.size == 1 && collection.links.size > 0
16
+ if collection.resources.first.links.empty?
17
+ collection.links.each { |link| link.source = collection.resources.first }
18
+ collection.resources.first.links = collection.links
19
+ end
20
+ end
21
+
22
+ # TODO: replace the following mechanism with one in the Links class
23
+ # replace link locations with link objects in all resources
24
+ collection.resources.each do |resource|
25
+ resource.links.collect! do |resource_link|
26
+ lnk = collection.links.select { |link| resource_link == link.to_s }.first
27
+ lnk ||= resource_link
28
+ end
29
+ end
30
+ collection
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module Occi
2
+ module Parser
3
+ module Ova
4
+
5
+ # @param [String] string
6
+ # @return [Occi::Collection]
7
+ def self.collection(string)
8
+ tar = Gem::Package::TarReader.new(StringIO.new(string))
9
+ ovf = mf = cert = nil
10
+ files = {}
11
+ tar.each do |entry|
12
+ tempfile = Tempfile.new(entry.full_name)
13
+ tempfile.write(entry.read)
14
+ tempfile.close
15
+ files[entry.full_name] = tempfile.path
16
+ ovf = tempfile.path if entry.full_name.end_with? '.ovf'
17
+ mf = tempfile.path if entry.full_name.end_with? '.mf'
18
+ cert = tempfile.path if entry.full_name.end_with? '.cert'
19
+ end
20
+
21
+ File.read(mf).each_line do |line|
22
+ name = line.scan(/SHA1\(([^\)]*)\)= (.*)/).flatten.first
23
+ sha1 = line.scan(/SHA1\(([^\)]*)\)= (.*)/).flatten.last
24
+ Occi::Log.debug "SHA1 hash #{Digest::SHA1.hexdigest(files[name])}"
25
+ raise "SHA1 mismatch for file #{name}" if Digest::SHA1.hexdigest(File.read(files[name])) != sha1
26
+ end if mf
27
+
28
+ raise 'no ovf file found' if ovf.nil?
29
+
30
+ Occi::Parser::Ovf.collection(File.read(ovf), files)
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,154 @@
1
+ module Occi
2
+ module Parser
3
+ module Ovf
4
+
5
+ # Declaring Class constants for OVF XML namespaces (defined in OVF specification ver.1.1)
6
+ OVF ="http://schemas.dmtf.org/ovf/envelope/1"
7
+ RASD ="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
8
+ VSSD ="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
9
+ OVFENV="http://schemas.dmtf.org/ovf/environment/1"
10
+ CIM ="http://schemas.dmtf.org/wbem/wscim/1/common"
11
+
12
+ # @param [String] string
13
+ # @param [Hash] files key value pairs of file names and paths to the file
14
+ def self.collection(string, files={})
15
+ collection = Occi::Collection.new
16
+ doc = Nokogiri::XML(string)
17
+ references = {}
18
+
19
+ doc.xpath('envelope:Envelope/envelope:References/envelope:File', 'envelope' => "#{OVF}").each do |file|
20
+ href = URI.parse(file.attributes['href'].to_s)
21
+ if href.relative?
22
+ if files[href.to_s]
23
+ references[file.attributes['id'].to_s] = 'file://' + files[href.to_s]
24
+ else
25
+ references[file.attributes['id'].to_s] = 'file://' + href.to_s
26
+ end
27
+ else
28
+ references[file.attributes['id'].to_s] = href.to_s
29
+ end
30
+ end
31
+
32
+ doc.xpath('envelope:Envelope/envelope:DiskSection/envelope:Disk', 'envelope' => "#{OVF}").each do |disk|
33
+ storage = Occi::Core::Resource.new('http://schemas.ogf.org/occi/infrastructure#storage')
34
+ if disk.attributes['fileRef']
35
+ storagelink = Occi::Core::Link.new("http://schemas.ogf.org/occi/infrastructure#storagelink")
36
+ storagelink.attributes.occi!.core!.title = disk.attributes['fileRef'].to_s
37
+ storagelink.attributes.occi!.core!.target = references[disk.attributes['fileRef'].to_s]
38
+ storage.attributes.occi!.core!.title = disk.attributes['diskId'].to_s
39
+ storage.links << storagelink
40
+ else
41
+ #OCCI accepts storage size in GB
42
+ #OVF ver 1.1: The capacity of a virtual disk shall be specified by the ovf:capacity attribute with an xs:long integer
43
+ #value. The default unit odf allocation shall be bytes. The optional string attribute
44
+ #ovf:capacityAllocationUnits may be used to specify a particular unit of allocation.
45
+ alloc_units = disk.attributes['capacityAllocationUnits'].to_s
46
+ if alloc_units.empty?
47
+ # The capacity is defined in bytes , convert to GB and pass it to OCCI
48
+ capacity = disk.attributes['capacity'].to_s
49
+ capacity =capacity.to_i
50
+ else
51
+ alloc_unit_bytes = self.alloc_units_bytes(alloc_units)
52
+ capacity = self.calculate_capacity_bytes(disk.attributes['capacity'].to_s, alloc_unit_bytes)
53
+ end
54
+ capacity_gb = self.calculate_capacity_gb(capacity)
55
+ Occi::Log.debug('capacity in gb ' + capacity_gb.to_s)
56
+ storage.attributes.occi!.storage!.size = capacity_gb.to_s if capacity_gb
57
+ storage.attributes.occi!.core!.title = disk.attributes['diskId'].to_s if disk.attributes['diskId']
58
+ end
59
+ collection.resources << storage
60
+ end
61
+
62
+ doc.xpath('envelope:Envelope/envelope:NetworkSection/envelope:Network', 'envelope' => "#{OVF}").each do |nw|
63
+ network = Occi::Core::Resource.new('http://schemas.ogf.org/occi/infrastructure#network')
64
+ network.attributes.occi!.core!.title = nw.attributes['name'].to_s
65
+ collection.resources << network
66
+ end
67
+
68
+ # Iteration through all the virtual hardware sections,and a sub-iteration on each Item defined in the Virtual Hardware section
69
+ doc.xpath('envelope:Envelope/envelope:VirtualSystem', 'envelope' => "#{OVF}").each do |virtsys|
70
+ compute = Occi::Core::Resource.new('http://schemas.ogf.org/occi/infrastructure#compute')
71
+
72
+ doc.xpath('envelope:Envelope/envelope:VirtualSystem/envelope:VirtualHardwareSection', 'envelope' => "#{OVF}").each do |virthwsec|
73
+ compute.attributes.occi!.core!.summary = virthwsec.xpath("item:Info/text()", 'item' => "#{RASD}").to_s
74
+
75
+ virthwsec.xpath('envelope:Item', 'envelope' => "#{OVF}").each do |resource_alloc|
76
+ resType = resource_alloc.xpath("item:ResourceType/text()", 'item' => "#{RASD}")
77
+ case resType.to_s
78
+ # 4 is the ResourceType for memory in the CIM_ResourceAllocationSettingData
79
+ when "4" then
80
+ Occi::Log.debug('calculating memory in gb ')
81
+ alloc_units = resource_alloc.xpath("item:AllocationUnits/text()", 'item' => "#{RASD}").to_s
82
+ Occi::Log.debug('allocated units in ovf file: ' + alloc_units)
83
+ alloc_unit_bytes = self.alloc_units_bytes(alloc_units)
84
+ capacity = self.calculate_capacity_bytes(resource_alloc.xpath("item:VirtualQuantity/text()", 'item' => "#{RASD}").to_s, alloc_unit_bytes)
85
+ capacity_gb = self.calculate_capacity_gb(capacity)
86
+ Occi::Log.debug('virtual quantity of memory configured in gb: ' + capacity_gb.to_s)
87
+ compute.attributes.occi!.compute!.memory = capacity_gb
88
+ # compute.attributes.occi!.compute!.memory = resource_alloc.xpath("item:VirtualQuantity/text()", 'item' => "#{RASD}").to_s.to_i
89
+ # 3 is the ResourceType for processor in the CIM_ResourceAllocationSettingData
90
+ when "3" then
91
+ compute.attributes.occi!.compute!.cores = resource_alloc.xpath("item:VirtualQuantity/text()", 'item' => "#{RASD}").to_s.to_i
92
+ when "10" then
93
+ networkinterface = Occi::Core::Link.new('http://schemas.ogf.org/occi/infrastructure#networkinterface')
94
+ networkinterface.attributes.occi!.core!.title = resource_alloc.xpath("item:ElementName/text()", 'item' => "#{RASD}").to_s
95
+ id = resource_alloc.xpath("item:Connection/text()", 'item' => "#{RASD}").to_s
96
+ network = collection.resources.select { |resource| resource.attributes.occi!.core!.title.to_s == id }.first
97
+ raise "Network with id #{id} not found" unless network
98
+ networkinterface.attributes.occi!.core!.target = network
99
+ when "17" then
100
+ storagelink = Occi::Core::Link.new("http://schemas.ogf.org/occi/infrastructure#storagelink")
101
+ storagelink.attributes.occi!.core!.title = resource_alloc.xpath("item:ElementName/text()", 'item' => "#{RASD}").to_s
102
+ # extract the mountpoint
103
+ host_resource = resource_alloc.xpath("item:HostResource/text()", 'item' => "#{RASD}").to_s
104
+ if host_resource.start_with? 'ovf:/disk/'
105
+ id = host_resource.gsub('ovf:/disk/', '')
106
+ storage = collection.resources.select { |resource| resource.attributes.occi!.core!.title.to_s == id }.first
107
+ raise "Disk with id #{id} not found" unless storage
108
+ storagelink.attributes.occi!.core!.target = storage
109
+ elsif host_resource.start_with? 'ovf:/file/'
110
+ id = host_resource.gsub('ovf:/file/', '')
111
+ storagelink.attributes.occi!.core!.target = references[id]
112
+ end
113
+ compute.links << storagelink
114
+ end
115
+ ##Add the cpu architecture
116
+ #system_sec = virthwsec.xpath('envelope:System', 'envelope' => "#{OVF}")
117
+ #virtsys_type = system_sec.xpath('vssd_:VirtualSystemType/text()', 'vssd_' => "#{VSSD}")
118
+ #compute.attributes.occi!.compute!.architecture = virtsys_type
119
+ end
120
+ end
121
+ collection.resources << compute
122
+ end
123
+ collection
124
+ end
125
+
126
+ ####################Helper method for calculation of storage size based on allocation units configured###########
127
+
128
+ def self.calculate_capacity_bytes(capacity, alloc_units_bytes)
129
+ total_capacity_bytes = alloc_units_bytes * capacity.to_i
130
+ total_capacity_bytes
131
+ end
132
+
133
+
134
+ def self.calculate_capacity_gb(capacity)
135
+ capacity_gb = capacity.to_f/(2**30)
136
+ capacity_gb
137
+ end
138
+
139
+
140
+ def self.alloc_units_bytes(alloc_units)
141
+ units = alloc_units.split('*')
142
+ #check units[1] is nil??
143
+ units[1].strip!
144
+ alloc_vars = units[1].split('^')
145
+ alloc_units_bytes = (alloc_vars[0].to_i**alloc_vars[1].to_i)
146
+ alloc_units_bytes
147
+ end
148
+
149
+ ###############End of Helper methods for OVF Parsing ##################################################################
150
+
151
+
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,234 @@
1
+ module Occi
2
+ module Parser
3
+ module Text
4
+ # Backwards compatibility for Ruby 1.8.7 and named groups in regular expressions
5
+ if RUBY_VERSION =~ /1.8/
6
+ require 'oniguruma'
7
+ REGEXP = Oniguruma::ORegexp
8
+ else
9
+ REGEXP = Regexp
10
+ end
11
+
12
+ # Regular expressions
13
+ REGEXP_QUOTED_STRING = /([^"\\]|\\.)*/
14
+ REGEXP_LOALPHA = /[a-z]/
15
+ REGEXP_DIGIT = /[0-9]/
16
+ REGEXP_INT = /#{REGEXP_DIGIT}*/
17
+ REGEXP_FLOAT = /#{REGEXP_DIGIT}*\.#{REGEXP_DIGIT}*/
18
+ REGEXP_NUMBER = /#{REGEXP_INT}|#{REGEXP_FLOAT}/
19
+ REGEXP_BOOL = /true|false/
20
+
21
+ # Regular expressions for OCCI
22
+ if Occi::Settings.compatibility
23
+ # Compatibility with terms starting with a number
24
+ REGEXP_TERM = /(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT})(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT}|-|_)*/
25
+ else
26
+ REGEXP_TERM = /#{REGEXP_LOALPHA}(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT}|-|_)*/
27
+ end
28
+ REGEXP_SCHEME = /#{URI::ABS_URI_REF}#/
29
+ REGEXP_TYPE_IDENTIFIER = /#{REGEXP_SCHEME}#{REGEXP_TERM}/
30
+ REGEXP_CLASS = /action|mixin|kind/
31
+
32
+ REGEXP_ATTR_COMPONENT = /#{REGEXP_LOALPHA}(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT}|-|_)*/
33
+ REGEXP_ATTRIBUTE_NAME = /#{REGEXP_ATTR_COMPONENT}(\.#{REGEXP_ATTR_COMPONENT})*/
34
+ REGEXP_ATTRIBUTE_PROPERTY = /immutable|required/
35
+ REGEXP_ATTRIBUTE_DEF = /(#{REGEXP_ATTRIBUTE_NAME})(\{#{REGEXP_ATTRIBUTE_PROPERTY}(\s+#{REGEXP_ATTRIBUTE_PROPERTY})*\})?/
36
+ REGEXP_ATTRIBUTE_LIST = /#{REGEXP_ATTRIBUTE_DEF}(\s+#{REGEXP_ATTRIBUTE_DEF})*/
37
+ REGEXP_ATTRIBUTE_REPR = /#{REGEXP_ATTRIBUTE_NAME}=("#{REGEXP_QUOTED_STRING}"|#{REGEXP_NUMBER}|#{REGEXP_BOOL})/
38
+
39
+ REGEXP_ACTION = /#{REGEXP_TYPE_IDENTIFIER}/
40
+ REGEXP_ACTION_LIST = /#{REGEXP_ACTION}(\s+#{REGEXP_ACTION})*/
41
+
42
+ REGEXP_RESOURCE_TYPE = /#{REGEXP_TYPE_IDENTIFIER}(\s+#{REGEXP_TYPE_IDENTIFIER})*/
43
+ REGEXP_LINK_INSTANCE = /#{URI::URI_REF}/
44
+ REGEXP_LINK_TYPE = /#{REGEXP_TYPE_IDENTIFIER}(\s+#{REGEXP_TYPE_IDENTIFIER})*/
45
+
46
+ # Regular expression for OCCI Categories
47
+ if Occi::Settings.compatibility
48
+ REGEXP_CATEGORY = "Category:\\s*(?<term>#{REGEXP_TERM})" << # term (mandatory)
49
+ ";\\s*scheme=\"(?<scheme>#{REGEXP_SCHEME})#{REGEXP_TERM}?\"" << # scheme (mandatory)
50
+ ";\\s*class=\"(?<class>#{REGEXP_CLASS})\"" << # class (mandatory)
51
+ "(;\\s*title=\"(?<title>#{REGEXP_QUOTED_STRING})\")?" << # title (optional)
52
+ "(;\\s*rel=\"(?<rel>#{REGEXP_TYPE_IDENTIFIER})\")?"<< # rel (optional)
53
+ "(;\\s*location=\"(?<location>#{URI::URI_REF})\")?" << # location (optional)
54
+ "(;\\s*attributes=\"(?<attributes>#{REGEXP_ATTRIBUTE_LIST})\")?" << # attributes (optional)
55
+ "(;\\s*actions=\"(?<actions>#{REGEXP_ACTION_LIST})\")?" << # actions (optional)
56
+ ';?' # additional semicolon at the end (not specified, for interoperability)
57
+ else
58
+ REGEXP_CATEGORY = "Category:\\s*(?<term>#{REGEXP_TERM})" << # term (mandatory)
59
+ ";\\s*scheme=\"(?<scheme>#{REGEXP_SCHEME})\"" << # scheme (mandatory)
60
+ ";\\s*class=\"(?<class>#{REGEXP_CLASS})\"" << # class (mandatory)
61
+ "(;\\s*title=\"(?<title>#{REGEXP_QUOTED_STRING})\")?" << # title (optional)
62
+ "(;\\s*rel=\"(?<rel>#{REGEXP_TYPE_IDENTIFIER})\")?"<< # rel (optional)
63
+ "(;\\s*location=\"(?<location>#{URI::URI_REF})\")?" << # location (optional)
64
+ "(;\\s*attributes=\"(?<attributes>#{REGEXP_ATTRIBUTE_LIST})\")?" << # attributes (optional)
65
+ "(;\\s*actions=\"(?<actions>#{REGEXP_ACTION_LIST})\")?" << # actions (optional)
66
+ ';?' # additional semicolon at the end (not specified, for interoperability)
67
+ end
68
+
69
+ # Regular expression for OCCI Link Instance References
70
+ REGEXP_LINK = "Link:\\s*\\<(?<uri>#{URI::URI_REF})\\>" << # uri (mandatory)
71
+ ";\\s*rel=\"(?<rel>#{REGEXP_RESOURCE_TYPE})\"" << # rel (mandatory)
72
+ "(;\\s*self=\"(?<self>#{REGEXP_LINK_INSTANCE})\")?" << # self (optional)
73
+ "(;\\s*category=\"(?<category>#{REGEXP_LINK_TYPE})\")?" << # category (optional)
74
+ "(?<attributes>(;\\s*(#{REGEXP_ATTRIBUTE_REPR}))*)" << # attributes (optional)
75
+ ';?' # additional semicolon at the end (not specified, for interoperability)
76
+
77
+ # Regular expression for OCCI Entity Attributes
78
+ REGEXP_ATTRIBUTE = "X-OCCI-Attribute:\\s*(?<name>#{REGEXP_ATTRIBUTE_NAME})=(\"(?<string>#{REGEXP_QUOTED_STRING})\"|(?<number>#{REGEXP_NUMBER})|(?<bool>#{REGEXP_BOOL}))" <<
79
+ ';?' # additional semicolon at the end (not specified, for interoperability)
80
+
81
+ # Regular expression for OCCI Location
82
+ REGEXP_LOCATION = "X-OCCI-Location:\\s*(?<location>#{URI::URI_REF})" <<
83
+ ';?' # additional semicolon at the end (not specified, for interoperability)
84
+
85
+
86
+ def self.categories(lines)
87
+ collection = Occi::Collection.new
88
+ lines.each do |line|
89
+ line.strip!
90
+ category = self.category(line) if line.start_with? 'Category:'
91
+ collection << category if category.kind_of? Occi::Core::Category
92
+ end
93
+ collection
94
+ end
95
+
96
+ def self.resource(lines)
97
+ collection = Occi::Collection.new
98
+ resource = Occi::Core::Resource.new
99
+ lines.each do |line|
100
+ line.strip!
101
+ case line
102
+ when /^Category:/
103
+ category = self.category(line)
104
+ resource.kind = category if category.kind_of? Occi::Core::Kind
105
+ resource.mixins << category if category.kind_of? Occi::Core::Mixin
106
+ when /^X-OCCI-Attribute:/
107
+ resource.attributes.merge! self.attribute(line)
108
+ when /^Link:/
109
+ resource.links << self.link_string(line, resource)
110
+ end
111
+ end
112
+ collection << resource if resource.kind_of? Occi::Core::Resource
113
+ collection
114
+ end
115
+
116
+ def self.link(lines)
117
+ collection = Occi::Collection.new
118
+ link = Occi::Core::Link.new
119
+ lines.each do |line|
120
+ line.strip!
121
+ case line
122
+ when /^Category:/
123
+ category = self.category(line)
124
+ link.kind = category if category.kind_of? Occi::Core::Kind
125
+ link.mixins << category if category.kind_of? Occi::Core::Mixin
126
+ when /^X-OCCI-Attribute:/
127
+ link.attributes.merge! self.attribute(line)
128
+ end
129
+ end
130
+ collection << link if link.kind_of? Occi::Core::Link
131
+ collection
132
+ end
133
+
134
+ def self.locations(lines)
135
+ locations = []
136
+ lines.each do |line|
137
+ line.strip!
138
+ locations << self.location(line) if line.start_with? 'X-OCCI-Location:'
139
+ end
140
+ locations
141
+ end
142
+
143
+ private
144
+
145
+ def self.category(string)
146
+ # create regular expression from regexp string
147
+ regexp = REGEXP.new(REGEXP_CATEGORY)
148
+ # match string to regular expression
149
+ match = regexp.match string
150
+
151
+ raise "could not match #{string}" unless match
152
+
153
+ term = match[:term]
154
+ scheme = match[:scheme]
155
+ title = match[:title]
156
+ related = match[:rel].to_s.split
157
+ attributes = Occi::Core::Attributes.new
158
+ if match[:attributes]
159
+ match[:attributes].split.each do |attribute|
160
+ property_string = attribute[/#{REGEXP_ATTRIBUTE_DEF}/, -2]
161
+ properties = Occi::Core::Properties.new
162
+ if property_string
163
+ properties.required = true if property_string.include? 'required'
164
+ properties.mutable = false if property_string.include? 'immutable'
165
+ end
166
+ name = attribute[/#{REGEXP_ATTRIBUTE_DEF}/, 1]
167
+ attributes.merge! name.split('.').reverse.inject(properties) { |a, n| Occi::Core::Attributes.new(n => a) }
168
+ end
169
+ end
170
+ actions = match[:actions].to_s.split
171
+ location = match[:location]
172
+ case match[:class]
173
+ when 'kind'
174
+ Occi::Core::Kind.new scheme, term, title, attributes, related, actions, location
175
+ when 'mixin'
176
+ Occi::Core::Mixin.new scheme, term, title, attributes, related, actions, location
177
+ when 'action'
178
+ Occi::Core::Action.new scheme, term, title, attributes
179
+ else
180
+ raise "Category with class #{match[:class]} not recognized in string: #{string}"
181
+ end
182
+ end
183
+
184
+ def self.attribute(string)
185
+ # create regular expression from regexp string
186
+ regexp = REGEXP.new(REGEXP_ATTRIBUTE)
187
+ # match string to regular expression
188
+ match = regexp.match string
189
+
190
+ raise "could not match #{string}" unless match
191
+
192
+ value = match[:string] if match[:string]
193
+ value = match[:number].to_i if match[:number]
194
+ value = match[:bool] == "true" if match[:bool]
195
+ Occi::Core::Attributes.split match[:name] => value
196
+ end
197
+
198
+ def self.link_string(string, source)
199
+ # create regular expression from regexp string
200
+ regexp = REGEXP.new(REGEXP_LINK)
201
+ # match string to regular expression
202
+ match = regexp.match string
203
+
204
+ raise "could not match #{string}" unless match
205
+
206
+ categories = match[:category].split
207
+ kind = categories.shift
208
+ mixins = categories
209
+ actions = nil
210
+ rel = match[:rel]
211
+ target = match[:uri]
212
+ location = match[:self]
213
+
214
+ # create an array of the list of attributes
215
+ attributes = match[:attributes].sub(/^\s*;\s*/, '').split ';'
216
+ # parse each attribute and create an OCCI Attribute object from it
217
+ attributes = attributes.inject(Hashie::Mash.new) { |hsh, attribute| hsh.merge!(Occi::Parser::Text.attribute('X-OCCI-Attribute: ' + attribute)) }
218
+ Occi::Core::Link.new kind, mixins, attributes, actions, rel, target, source, location
219
+ end
220
+
221
+ def self.location(string)
222
+ # create regular expression from regexp string
223
+ regexp = REGEXP.new(REGEXP_LOCATION)
224
+ # match string to regular expression
225
+ match = regexp.match string
226
+
227
+ raise "could not match #{string}" unless match
228
+
229
+ match[:location]
230
+ end
231
+
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,18 @@
1
+ module Occi
2
+ module Parser
3
+ module Xml
4
+ # @param [String] string
5
+ # @return [Occi::Collection]
6
+ def self.collection(string)
7
+ collection = Occi::Collection.new
8
+ hash = Hashie::Mash.new(Hash.from_xml(Nokogiri::XML(string)))
9
+ collection.kinds.merge hash.kinds.collect { |kind| Occi::Core::Kind.new(kind.scheme, kind.term, kind.title, kind.attributes, kind.related, kind.actions) } if hash.kinds
10
+ collection.mixins.merge hash.mixins.collect { |mixin| Occi::Core::Mixin.new(mixin.scheme, mixin.term, mixin.title, mixin.attributes, mixin.related, mixin.actions) } if hash.mixins
11
+ collection.actions.merge hash.actions.collect { |action| Occi::Core::Action.new(action.scheme, action.term, action.title, action.attributes) } if hash.actions
12
+ collection.resources.merge hash.resources.collect { |resource| Occi::Core::Resource.new(resource.kind, resource.mixins, resource.attributes, resource.actions, resource.links) } if hash.resources
13
+ collection.links.merge hash.links.collect { |link| Occi::Core::Link.new(link.kind, link.mixins, link.attributes) } if hash.links
14
+ collection
15
+ end
16
+ end
17
+ end
18
+ end