occi-core 4.0.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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