ibm_power_hmc 0.17.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'uri'
5
+
6
+ module IbmPowerHmc
7
+ ##
8
+ # Generic parser for HMC K2 XML responses.
9
+ class Parser
10
+ def initialize(body)
11
+ @doc = REXML::Document.new(body)
12
+ end
13
+
14
+ ##
15
+ # @!method entry
16
+ # Return the first K2 entry element in the response.
17
+ # @return [REXML::Element, nil] The first entry element.
18
+ def entry
19
+ @doc.elements["entry"]
20
+ end
21
+
22
+ ##
23
+ # @!method object(filter_type = nil)
24
+ # Parse the first K2 entry element into an object.
25
+ # @param filter_type [String] Entry type must match the specified type.
26
+ # @return [IbmPowerHmc::AbstractRest, nil] The parsed object.
27
+ def object(filter_type = nil)
28
+ self.class.to_obj(entry, filter_type)
29
+ end
30
+
31
+ def self.to_obj(entry, filter_type = nil)
32
+ return if entry.nil?
33
+
34
+ content = entry.elements["content[@type]"]
35
+ return if content.nil?
36
+
37
+ type = content.attributes["type"].split("=")[1] || filter_type.to_s
38
+ return unless filter_type.nil? || filter_type.to_s == type
39
+
40
+ Module.const_get("IbmPowerHmc::#{type}").new(entry)
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Parser for HMC K2 feeds.
46
+ # A feed encapsulates a list of entries like this:
47
+ # <feed>
48
+ # <entry>
49
+ # <!-- entry #1 -->
50
+ # </entry>
51
+ # <entry>
52
+ # <!-- entry #2 -->
53
+ # </entry>
54
+ # ...
55
+ # </feed>
56
+ class FeedParser < Parser
57
+ def entries
58
+ objs = []
59
+ @doc.each_element("feed/entry") do |entry|
60
+ objs << yield(entry)
61
+ end
62
+ objs
63
+ end
64
+
65
+ ##
66
+ # @!method objects(filter_type = nil)
67
+ # Parse feed entries into objects.
68
+ # @param filter_type [String] Filter entries based on content type.
69
+ # @return [Array<IbmPowerHmc::AbstractRest>] The list of objects.
70
+ def objects(filter_type = nil)
71
+ entries do |entry|
72
+ self.class.to_obj(entry, filter_type)
73
+ end.compact
74
+ end
75
+ end
76
+
77
+ ##
78
+ # HMC generic K2 non-REST object.
79
+ # @abstract
80
+ # @attr_reader [REXML::Document] xml The XML document representing this object.
81
+ class AbstractNonRest
82
+ ATTRS = {}.freeze
83
+ attr_reader :xml
84
+
85
+ def initialize(xml)
86
+ @xml = xml
87
+ self.class::ATTRS.each { |varname, xpath| define_attr(varname, xpath) }
88
+ end
89
+
90
+ ##
91
+ # @!method define_attr(varname, xpath)
92
+ # Define an instance variable using the text of an XML element as value.
93
+ # @param varname [String] The name of the instance variable.
94
+ # @param xpath [String] The XPath of the XML element containing the text.
95
+ def define_attr(varname, xpath)
96
+ value = singleton(xpath)
97
+ self.class.__send__(:attr_reader, varname)
98
+ self.class.__send__(:define_method, "#{varname}=") do |v|
99
+ if v.nil?
100
+ xml.elements.delete(xpath)
101
+ else
102
+ create_element(xpath) if xml.elements[xpath].nil?
103
+ xml.elements[xpath].text = v
104
+ end
105
+ instance_variable_set("@#{varname}", v)
106
+ end
107
+ instance_variable_set("@#{varname}", value)
108
+ end
109
+ private :define_attr
110
+
111
+ ##
112
+ # @!method create_element(xpath)
113
+ # Create a new XML element.
114
+ # @param xpath [String] The XPath of the XML element to create.
115
+ def create_element(xpath)
116
+ cur = xml
117
+ xpath.split("/").each do |el|
118
+ p = cur.elements[el]
119
+ if p.nil?
120
+ cur = cur.add_element(el)
121
+ else
122
+ cur = p
123
+ end
124
+ end
125
+ end
126
+
127
+ ##
128
+ # @!method singleton(xpath, attr = nil)
129
+ # Get the text (or the value of a specified attribute) of an XML element.
130
+ # @param xpath [String] The XPath of the XML element.
131
+ # @param attr [String] The name of the attribute.
132
+ # @return [String, nil] The text or attribute value of the XML element or nil.
133
+ # @example lpar.singleton("PartitionProcessorConfiguration/*/MaximumVirtualProcessors").to_i
134
+ def singleton(xpath, attr = nil)
135
+ elem = xml.elements[xpath]
136
+ return if elem.nil?
137
+
138
+ attr.nil? ? elem.text&.strip : elem.attributes[attr]
139
+ end
140
+
141
+ def to_s
142
+ str = +"#{self.class.name}:\n"
143
+ self.class::ATTRS.each do |varname, _|
144
+ value = instance_variable_get("@#{varname}")
145
+ value = value.nil? ? "null" : "'#{value}'"
146
+ str << " #{varname}: #{value}\n"
147
+ end
148
+ str
149
+ end
150
+
151
+ def uuid_from_href(href, index = -1)
152
+ URI(href).path.split('/')[index]
153
+ end
154
+
155
+ def uuids_from_links(elem, index = -1)
156
+ xml.get_elements("#{elem}/link[@href]").map do |link|
157
+ uuid_from_href(link.attributes["href"], index)
158
+ end.compact
159
+ end
160
+
161
+ def timestamp(xpath)
162
+ # XML element containing a number of milliseconds since the Epoch.
163
+ Time.at(0, singleton(xpath).to_i, :millisecond).utc
164
+ end
165
+
166
+ def collection_of(name, type)
167
+ xml.get_elements([name, type].compact.join("/")).map do |elem|
168
+ Module.const_get("IbmPowerHmc::#{elem.name}").new(elem)
169
+ rescue NameError
170
+ nil
171
+ end.compact
172
+ end
173
+ end
174
+
175
+ ##
176
+ # HMC generic K2 REST object.
177
+ # Encapsulate data for a single REST object.
178
+ # The XML looks like this:
179
+ # <entry>
180
+ # <id>uuid</id>
181
+ # <published>timestamp</published>
182
+ # <link rel="SELF" href="https://..."/>
183
+ # <etag:etag>ETag</etag:etag>
184
+ # <content type="type">
185
+ # <!-- actual content here -->
186
+ # </content>
187
+ # </entry>
188
+ #
189
+ # @abstract
190
+ # @attr_reader [String] uuid The UUID of the object contained in the entry.
191
+ # @attr_reader [Time] published The time at which the entry was published.
192
+ # @attr_reader [URI::HTTPS] href The URL of the object itself.
193
+ # @attr_reader [String] etag The entity tag of the entry.
194
+ # @attr_reader [String] content_type The content type of the object contained in the entry.
195
+ class AbstractRest < AbstractNonRest
196
+ attr_reader :uuid, :published, :href, :etag, :content_type
197
+
198
+ def initialize(entry)
199
+ if entry.name != "entry"
200
+ # We are inlined.
201
+ super(entry)
202
+ return
203
+ end
204
+
205
+ @uuid = entry.elements["id"]&.text
206
+ @published = Time.xmlschema(entry.elements["published"]&.text)
207
+ link = entry.elements["link[@rel='SELF']"]
208
+ @href = URI(link.attributes["href"]) unless link.nil?
209
+ @etag = entry.elements["etag:etag"]&.text&.strip
210
+ content = entry.elements["content"]
211
+ @content_type = content.attributes["type"]
212
+ super(content.elements.first)
213
+ end
214
+
215
+ def to_s
216
+ str = super
217
+ str << " uuid: '#{uuid}'\n" if defined?(@uuid)
218
+ str << " published: '#{published}'\n" if defined?(@published)
219
+ str
220
+ end
221
+ end
222
+
223
+ # Error response from HMC
224
+ class HttpErrorResponse < AbstractRest
225
+ ATTRS = {
226
+ :status => "HTTPStatus",
227
+ :uri => "RequestURI",
228
+ :reason => "ReasonCode",
229
+ :message => "Message"
230
+ }.freeze
231
+ end
232
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IbmPowerHmc
4
+ # Performance and Capacity Monitoring preferences
5
+ class ManagementConsolePcmPreference < AbstractRest
6
+ ATTRS = {
7
+ :max_ltm => "MaximumManagedSystemsForLongTermMonitor",
8
+ :max_compute_ltm => "MaximumManagedSystemsForComputeLTM",
9
+ :max_aggregation => "MaximumManagedSystemsForAggregation",
10
+ :max_stm => "MaximumManagedSystemsForShortTermMonitor",
11
+ :max_em => "MaximumManagedSystemsForEnergyMonitor",
12
+ :aggregated_storage_duration => "AggregatedMetricsStorageDuration"
13
+ }.freeze
14
+
15
+ def managed_system_preferences
16
+ collection_of(nil, "ManagedSystemPcmPreference")
17
+ end
18
+ end
19
+
20
+ class ManagedSystemPcmPreference < AbstractNonRest
21
+ ATTRS = {
22
+ :id => "Metadata/Atom/AtomID",
23
+ :name => "SystemName",
24
+ :long_term_monitor => "LongTermMonitorEnabled",
25
+ :aggregation => "AggregationEnabled",
26
+ :short_term_monitor => "ShortTermMonitorEnabled",
27
+ :compute_ltm => "ComputeLTMEnabled",
28
+ :energy_monitor => "EnergyMonitorEnabled"
29
+ }.freeze
30
+ end
31
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IbmPowerHmc
4
+ # Serviceable Event
5
+ class ServiceableEvent < AbstractRest
6
+ ATTRS = {
7
+ :prob_uuid => "problemUuid",
8
+ :hostname => "reportingConsoleNode/hostName",
9
+ :number => "problemNumber",
10
+ :hw_record => "problemManagementHardwareRecord",
11
+ :description => "shortDescription",
12
+ :state => "problemState",
13
+ :approval_state => "ApprovalState",
14
+ :refcode => "referenceCode",
15
+ :refcode_ext => "referenceCodeExtension",
16
+ :refcode_sys => "systemReferenceCode",
17
+ :call_home => "callHomeEnabled",
18
+ :dup_count => "duplicateCount",
19
+ :severity => "eventSeverity",
20
+ :notif_type => "notificationType",
21
+ :notif_status => "notificationStatus",
22
+ :post_action => "postAction",
23
+ :symptom => "symptomString",
24
+ :lpar_id => "partitionId",
25
+ :lpar_name => "partitionName",
26
+ :lpar_hostname => "partitionHostName",
27
+ :lpar_ostype => "partitionOSType",
28
+ :syslog_id => "sysLogId",
29
+ :total_events => "totalEvents"
30
+ }.freeze
31
+
32
+ def reporting_mtms
33
+ mtms("reportingManagedSystemNode")
34
+ end
35
+
36
+ def failing_mtms
37
+ mtms("failingManagedSystemNode")
38
+ end
39
+
40
+ def time
41
+ timestamp("primaryTimestamp")
42
+ end
43
+
44
+ def created_time
45
+ timestamp("createdTimestamp")
46
+ end
47
+
48
+ def first_reported_time
49
+ timestamp("firstReportedTimestamp")
50
+ end
51
+
52
+ def last_reported_time
53
+ timestamp("lastReportedTimestamp")
54
+ end
55
+
56
+ def frus
57
+ collection_of("fieldReplaceableUnits", "FieldReplaceableUnit")
58
+ end
59
+
60
+ def ext_files
61
+ collection_of("extendedErrorData", "ExtendedFileData")
62
+ end
63
+
64
+ private
65
+
66
+ def mtms(prefix)
67
+ machtype = singleton("#{prefix}/managedTypeModelSerial/MachineType")
68
+ model = singleton("#{prefix}/managedTypeModelSerial/Model")
69
+ serial = singleton("#{prefix}/managedTypeModelSerial/SerialNumber")
70
+ "#{machtype}-#{model}*#{serial}"
71
+ end
72
+ end
73
+
74
+ class FieldReplaceableUnit < AbstractNonRest
75
+ ATTRS = {
76
+ :part_number => "partNumber",
77
+ :fru_class => "class",
78
+ :description => "fieldReplaceableUnitDescription",
79
+ :location => "locationCode",
80
+ :serial => "SerialNumber",
81
+ :ccin => "ccin"
82
+ }.freeze
83
+ end
84
+
85
+ class ExtendedFileData < AbstractNonRest
86
+ ATTRS = {
87
+ :filename => "fileName",
88
+ :description => "description",
89
+ :zipfilename => "zipFileName"
90
+ }.freeze
91
+ end
92
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IbmPowerHmc
4
+ class PartitionTemplateSummary < AbstractRest
5
+ ATTRS = {
6
+ :name => "partitionTemplateName"
7
+ }.freeze
8
+ end
9
+
10
+ class PartitionTemplate < AbstractRest
11
+ ATTRS = {
12
+ :name => "partitionTemplateName",
13
+ :description => "description",
14
+ :lpar_name => "logicalPartitionConfig/partitionName",
15
+ :lpar_type => "logicalPartitionConfig/partitionType",
16
+ :lpar_id => "logicalPartitionConfig/partitionId",
17
+ :os => "logicalPartitionConfig/osVersion",
18
+ :memory => "logicalPartitionConfig/memoryConfiguration/currMemory",
19
+ :dedicated => "logicalPartitionConfig/processorConfiguration/hasDedicatedProcessors",
20
+ :sharing_mode => "logicalPartitionConfig/processorConfiguration/sharingMode",
21
+ :vprocs => "logicalPartitionConfig/processorConfiguration/sharedProcessorConfiguration/desiredVirtualProcessors",
22
+ :proc_units => "logicalPartitionConfig/processorConfiguration/sharedProcessorConfiguration/desiredProcessingUnits",
23
+ :pool_id => "logicalPartitionConfig/processorConfiguration/sharedProcessorConfiguration/sharedProcessorPoolId",
24
+ :procs => "logicalPartitionConfig/processorConfiguration/dedicatedProcessorConfiguration/desiredProcessors"
25
+ }.freeze
26
+
27
+ def vscsi
28
+ REXML::XPath.match(xml, 'logicalPartitionConfig/virtualSCSIClientAdapters/VirtualSCSIClientAdapter').map do |adap|
29
+ {
30
+ :vios => adap.elements['connectingPartitionName']&.text,
31
+ :physvol => adap.elements['associatedPhysicalVolume/PhysicalVolume/name']&.text,
32
+ }
33
+ end
34
+ end
35
+
36
+ def vscsi=(list = [])
37
+ adaps = REXML::Element.new('virtualSCSIClientAdapters')
38
+ adaps.add_attribute('schemaVersion', 'V1_5_0')
39
+ list.each do |vscsi|
40
+ adaps.add_element('VirtualSCSIClientAdapter', {'schemaVersion' => 'V1_5_0'}).tap do |v|
41
+ v.add_element('associatedLogicalUnits', {'schemaVersion' => 'V1_5_0'})
42
+ v.add_element('associatedPhysicalVolume', {'schemaVersion' => 'V1_5_0'}).tap do |e|
43
+ e.add_element('PhysicalVolume', {'schemaVersion' => 'V1_5_0'}).add_element('name').text = vscsi[:physvol] if vscsi[:physvol]
44
+ end
45
+ v.add_element('connectingPartitionName').text = vscsi[:vios]
46
+ v.add_element('AssociatedTargetDevices', {'schemaVersion' => 'V1_5_0'})
47
+ v.add_element('associatedVirtualOpticalMedia', {'schemaVersion' => 'V1_5_0'})
48
+ end
49
+ end
50
+ if xml.elements['logicalPartitionConfig/virtualSCSIClientAdapters']
51
+ xml.elements['logicalPartitionConfig/virtualSCSIClientAdapters'] = adaps
52
+ else
53
+ xml.elements['logicalPartitionConfig'].add_element(adaps)
54
+ end
55
+ end
56
+
57
+ def vfc
58
+ REXML::XPath.match(xml, 'logicalPartitionConfig/virtualFibreChannelClientAdapters/VirtualFibreChannelClientAdapter').map do |adap|
59
+ {
60
+ :vios => adap.elements['connectingPartitionName']&.text,
61
+ :port => adap.elements['portName']&.text
62
+ }
63
+ end
64
+ end
65
+
66
+ def vfc=(list = [])
67
+ adaps = REXML::Element.new('virtualFibreChannelClientAdapters')
68
+ adaps.add_attribute('schemaVersion', 'V1_5_0')
69
+ list.each do |vfc|
70
+ adaps.add_element('VirtualFibreChannelClientAdapter', {'schemaVersion' => 'V1_5_0'}).tap do |v|
71
+ v.add_element('connectingPartitionName').text = vfc[:vios]
72
+ v.add_element('portName').text = vfc[:port]
73
+ end
74
+ end
75
+ if xml.elements['logicalPartitionConfig/virtualFibreChannelClientAdapters']
76
+ xml.elements['logicalPartitionConfig/virtualFibreChannelClientAdapters'] = adaps
77
+ else
78
+ xml.elements['logicalPartitionConfig'].add_element(adaps)
79
+ end
80
+ end
81
+
82
+ def vlans
83
+ REXML::XPath.match(xml, 'logicalPartitionConfig/clientNetworkAdapters/ClientNetworkAdapter/clientVirtualNetworks/ClientVirtualNetwork').map do |vlan|
84
+ {
85
+ :name => vlan.elements['name']&.text,
86
+ :vlan_id => vlan.elements['vlanId']&.text,
87
+ :switch => vlan.elements['associatedSwitchName']&.text
88
+ }
89
+ end
90
+ end
91
+
92
+ def vlans=(list = [])
93
+ adaps = REXML::Element.new('clientNetworkAdapters')
94
+ adaps.add_attribute('schemaVersion', 'V1_5_0')
95
+ list.each do |vlan|
96
+ adaps.add_element('ClientNetworkAdapter', {'schemaVersion' => 'V1_5_0'})
97
+ .add_element('clientVirtualNetworks', {'schemaVersion' => 'V1_5_0'})
98
+ .add_element('ClientVirtualNetwork', {'schemaVersion' => 'V1_5_0'})
99
+ .tap do |v|
100
+ v.add_element('name').text = vlan[:name]
101
+ v.add_element('vlanId').text = vlan[:vlan_id]
102
+ v.add_element('associatedSwitchName').text = vlan[:switch]
103
+ end
104
+ end
105
+ xml.elements['logicalPartitionConfig/clientNetworkAdapters'] = adaps
106
+ end
107
+ end
108
+ end