Project

General

Profile

Download (14 KB) Statistics
| Branch: | Tag: | Revision:

hammer-cli-csv / lib / hammer_cli_csv / content_hosts.rb @ 5146ac68

1
module HammerCLICsv
2
  class CsvCommand
3
    class ContentHostsCommand < BaseCommand
4
      include ::HammerCLIForemanTasks::Helper
5

    
6
      command_name 'content-hosts'
7
      desc         'import or export content hosts'
8

    
9
      ORGANIZATION = 'Organization'
10
      ENVIRONMENT = 'Environment'
11
      CONTENTVIEW = 'Content View'
12
      HOSTCOLLECTIONS = 'Host Collections'
13
      VIRTUAL = 'Virtual'
14
      HOST = 'Host'
15
      OPERATINGSYSTEM = 'OS'
16
      ARCHITECTURE = 'Arch'
17
      SOCKETS = 'Sockets'
18
      RAM = 'RAM'
19
      CORES = 'Cores'
20
      SLA = 'SLA'
21
      PRODUCTS = 'Products'
22
      SUBSCRIPTIONS = 'Subscriptions'
23

    
24
      def export
25
        CSV.open(option_file || '/dev/stdout', 'wb', {:force_quotes => false}) do |csv|
26
          csv << [NAME, ORGANIZATION, ENVIRONMENT, CONTENTVIEW, HOSTCOLLECTIONS, VIRTUAL, HOST,
27
                  OPERATINGSYSTEM, ARCHITECTURE, SOCKETS, RAM, CORES, SLA, PRODUCTS, SUBSCRIPTIONS]
28
          if @server_status['release'] == 'Headpin'
29
            export_sam csv
30
          else
31
            export_foretello csv
32
          end
33
        end
34
      end
35

    
36
      def export_sam(csv)
37
        guests_hypervisor = {}
38
        host_ids = []
39

    
40
        @headpin.get(:organizations).each do |organization|
41
          next if option_organization && organization['name'] != option_organization
42
          host_ids = @headpin.get("organizations/#{organization['label']}/systems").collect do |host|
43
            host['guests'].each { |guest| guests_hypervisor[guest['uuid']] = host['name'] }
44
            host['uuid']
45
          end
46
        end
47

    
48
        host_ids.each do |host_id|
49
          host = @headpin.get("systems/#{host_id}")
50
          host_subscriptions = @headpin.get("systems/#{host_id}/subscriptions")['entitlements']
51

    
52
          name = host['name']
53
          organization_name = host['owner']['displayName']
54
          environment = host['environment']['name']
55
          contentview = host['content_view']['name']
56
          hostcollections = nil
57
          virtual = host['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
58
          hypervisor = guests_hypervisor[host['uuid']]
59
          if host['facts']['distribution.name']
60
            operatingsystem = "#{host['facts']['distribution.name']} "
61
            operatingsystem += host['facts']['distribution.version'] if host['facts']['distribution.version']
62
            operatingsystem.strip!
63
          end
64
          architecture = host['facts']['uname.machine']
65
          sockets = host['facts']['cpu.cpu_socket(s)']
66
          ram = host['facts']['memory.memtotal']
67
          cores = host['facts']['cpu.core(s)_per_socket'] || 1
68
          sla = host['serviceLevel']
69

    
70
          products = CSV.generate do |column|
71
            column << host['installedProducts'].collect do |product|
72
              "#{product['productId']}|#{product['productName']}"
73
            end
74
          end
75
          products.delete!("\n")
76

    
77
          subscriptions = CSV.generate do |column|
78
            column << host_subscriptions.collect do |subscription|
79
              "#{subscription['quantity']}|#{subscription['productId']}|#{subscription['poolName']}"
80
            end
81
          end
82
          subscriptions.delete!("\n")
83

    
84
          csv << [name, organization_name, environment, contentview, hostcollections, virtual, hypervisor,
85
                  operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
86
        end
87
      end
88

    
89
      def export_foretello(csv)
90
        @api.resource(:organizations).call(:index, {:per_page => 999999})['results'].each do |organization|
91
          next if option_organization && organization['name'] != option_organization
92

    
93
          @api.resource(:systems).call(:index, {
94
              'per_page' => 999999,
95
              'organization_id' => foreman_organization(:name => organization['name'])
96
          })['results'].each do |host|
97
            host = @api.resource(:systems).call(:show, {
98
                'id' => host['uuid'],
99
                'fields' => 'full'
100
            })
101

    
102
            name = host['name']
103
            organization_name = organization['name']
104
            environment = host['environment']['label']
105
            contentview = host['content_view']['name']
106
            hostcollections = CSV.generate do |column|
107
              column << host['hostCollections'].collect do |hostcollection|
108
                hostcollection['name']
109
              end
110
            end
111
            hostcollections.delete!("\n")
112
            virtual = host['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
113
            hypervisor_host = host['virtual_host'].nil? ? nil : host['virtual_host']['name']
114
            operatingsystem = "#{host['facts']['distribution.name']} " if host['facts']['distribution.name']
115
            operatingsystem += host['facts']['distribution.version'] if host['facts']['distribution.version']
116
            architecture = host['facts']['uname.machine']
117
            sockets = host['facts']['cpu.cpu_socket(s)']
118
            ram = host['facts']['memory.memtotal']
119
            cores = host['facts']['cpu.core(s)_per_socket'] || 1
120
            sla = ''
121
            products = CSV.generate do |column|
122
              column << host['installedProducts'].collect do |product|
123
                "#{product['productId']}|#{product['productName']}"
124
              end
125
            end
126
            products.delete!("\n")
127
            subscriptions = CSV.generate do |column|
128
              column << @api.resource(:subscriptions).call(:index, {
129
                  'organization_id' => organization['id'],
130
                  'system_id' => host['uuid']
131
              })['results'].collect do |subscription|
132
                "#{subscription['consumed']}|#{subscription['product_id']}|#{subscription['product_name']}"
133
              end
134
            end
135
            subscriptions.delete!("\n")
136
            csv << [name, organization_name, environment, contentview, hostcollections, virtual, hypervisor_host,
137
                    operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
138
          end
139
        end
140
      end
141

    
142
      def import
143
        remote = @server_status['plugins'].detect { |plugin| plugin['name'] == 'foreman_csv' }
144
        if remote.nil?
145
          import_locally
146
        else
147
          import_remotely
148
        end
149
      end
150

    
151
      def import_remotely
152
        params = {'content' => ::File.new(::File.expand_path(option_file), 'rb')}
153
        headers = {:content_type => 'multipart/form-data', :multipart => true}
154
        task_progress(@api.resource(:csv).call(:import_content_hosts, params, headers))
155
      end
156

    
157
      def import_locally
158
        @existing = {}
159
        @hypervisor_guests = {}
160

    
161
        thread_import do |line|
162
          create_content_hosts_from_csv(line)
163
        end
164

    
165
        if !@hypervisor_guests.empty?
166
          print(_('Updating hypervisor and guest associations...')) if option_verbose?
167
          @hypervisor_guests.each do |host_id, guest_ids|
168
            @api.resource(:hosts).call(:update, {
169
                'id' => host_id,
170
                'host' => {
171
                    'guest_ids' => guest_ids
172
                }
173
            })
174
          end
175
          puts _('done') if option_verbose?
176
        end
177
      end
178

    
179
      def create_content_hosts_from_csv(line)
180
        return if option_organization && line[ORGANIZATION] != option_organization
181

    
182
        if !@existing[line[ORGANIZATION]]
183
          @existing[line[ORGANIZATION]] = true
184
          # Fetching all content hosts is too slow and times out due to the complexity of the data
185
          # rendered in the json.
186
          # http://projects.theforeman.org/issues/6307
187
          total = @api.resource(:hosts).call(:index, {
188
              'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
189
              'per_page' => 1
190
          })['total'].to_i
191
          (total / 20 + 2).to_i.times do |page|
192
            @api.resource(:hosts).call(:index, {
193
                'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
194
                'page' => page + 1,
195
                'per_page' => 20
196
            })['results'].each do |host|
197
              @existing[host['name']] = {
198
                :host => host['id'],
199
                :subscription => host['subscription']['id'],
200
                :content => host['content']['id']
201
              }
202
            end
203
          end
204
        end
205

    
206
        count(line[COUNT]).times do |number|
207
          name = namify(line[NAME], number)
208

    
209
          if !@existing.include? name
210
            print(_("Creating content host '%{name}'...") % {:name => name}) if option_verbose?
211
            host_id = @api.resource(:host_subscriptions).call(:register, {
212
                'name' => name,
213
                'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
214
                'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
215
                'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW]),
216
                'facts' => facts(name, line),
217
                'installed_products' => products(line),
218
                'service_level' => line[SLA],
219
                'type' => 'system'
220
            })['id']
221
            @existing[name] = host_id
222
          else
223
            # TODO: remove passing facet IDs to update
224
            # Bug #13849 - updating a host's facet should not require the facet id to be included in facet params
225
            #              http://projects.theforeman.org/issues/13849
226
            print(_("Updating content host '%{name}'...") % {:name => name}) if option_verbose?
227
            host_id = @api.resource(:hosts).call(:update, {
228
                'id' => @existing[name][:host],
229
                'host' => {
230
                    'name' => name,
231
                    'content_facet_attributes' => {
232
                        'id' => @existing[name][:content],
233
                        'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
234
                        'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW])
235
                    },
236
                    'subscription_facet_attributes' => {
237
                        'id' => @existing[name][:subscription],
238
                        'facts' => facts(name, line),
239
                        # TODO: PUT /hosts subscription_facet_attributes missing "installed_products"
240
                        # http://projects.theforeman.org/issues/13854
241
                        #'installed_products' => products(line),
242
                        'service_level' => line[SLA]
243
                    }
244
                }
245
            })['host_id']
246
          end
247

    
248
          if line[VIRTUAL] == 'Yes' && line[HOST]
249
            raise "Content host '#{line[HOST]}' not found" if !@existing[line[HOST]]
250
            @hypervisor_guests[@existing[line[HOST]]] ||= []
251
            @hypervisor_guests[@existing[line[HOST]]] << @existing[name]
252
          end
253

    
254
          update_host_facts(host_id, line)
255
          update_host_collections(host_id, line)
256
          update_subscriptions(host_id, line)
257

    
258
          puts _('done') if option_verbose?
259
        end
260
      rescue RuntimeError => e
261
        raise "#{e}\n       #{line}"
262
      end
263

    
264
      private
265

    
266
      def facts(name, line)
267
        facts = {}
268
        facts['system.certificate_version'] = '3.2'  # Required for auto-attach to work
269
        facts['network.hostname'] = name
270
        facts['cpu.core(s)_per_socket'] = line[CORES]
271
        facts['cpu.cpu_socket(s)'] = line[SOCKETS]
272
        facts['memory.memtotal'] = line[RAM]
273
        facts['uname.machine'] = line[ARCHITECTURE]
274
        (facts['distribution.name'], facts['distribution.version']) = os_name_version(line[OPERATINGSYSTEM])
275
        facts['virt.is_guest'] = line[VIRTUAL] == 'Yes' ? true : false
276
        facts['virt.uuid'] = "#{line[ORGANIZATION]}/#{name}" if facts['virt.is_guest']
277
        facts['cpu.cpu(s)'] = 1
278
        facts
279
      end
280

    
281
      def update_host_facts(host_id, line)
282
      end
283

    
284
      def update_host_collections(host_id, line)
285
        return nil if !line[HOSTCOLLECTIONS]
286
        CSV.parse_line(line[HOSTCOLLECTIONS]).each do |hostcollection_name|
287
          @api.resource(:host_collections).call(:add_hosts, {
288
              'id' => katello_hostcollection(line[ORGANIZATION], :name => hostcollection_name),
289
              'hosts_ids' => [host_id]
290
          })
291
        end
292
      end
293

    
294
      def os_name_version(operatingsystem)
295
        if operatingsystem.nil?
296
          name = nil
297
          version = nil
298
        elsif operatingsystem.index(' ')
299
          (name, version) = operatingsystem.split(' ')
300
        else
301
          (name, version) = ['RHEL', operatingsystem]
302
        end
303
        [name, version]
304
      end
305

    
306
      def products(line)
307
        return nil if !line[PRODUCTS]
308
        products = CSV.parse_line(line[PRODUCTS]).collect do |product_details|
309
          product = {}
310
          # TODO: these get passed straight through to candlepin; probably would be better to process in server
311
          #       to allow underscore product_id here
312
          (product['productId'], product['productName']) = product_details.split('|')
313
          product['arch'] = line[ARCHITECTURE]
314
          product['version'] = os_name_version(line[OPERATINGSYSTEM])[1]
315
          product
316
        end
317
        products
318
      end
319

    
320
      def update_subscriptions(host_id, line)
321
        existing_subscriptions = @api.resource(:host_subscriptions).call(:index, {
322
            'host_id' => host_id
323
        })['results']
324
        if existing_subscriptions.length != 0
325
          @api.resource(:host_subscriptions).call(:remove_subscriptions, {
326
            'host_id' => host_id,
327
            'subscriptions' => existing_subscriptions
328
          })
329
        end
330

    
331
        return if line[SUBSCRIPTIONS].nil? || line[SUBSCRIPTIONS].empty?
332

    
333
        subscriptions = CSV.parse_line(line[SUBSCRIPTIONS], {:skip_blanks => true}).collect do |details|
334
          (amount, sku, name) = details.split('|')
335
          {
336
            :id => katello_subscription(line[ORGANIZATION], :name => name),
337
            :quantity => (amount.nil? || amount.empty? || amount == 'Automatic') ? 0 : amount.to_i
338
          }
339
        end
340

    
341
        @api.resource(:host_subscriptions).call(:add_subscriptions, {
342
            'host_id' => host_id,
343
            'subscriptions' => subscriptions
344
        })
345
      end
346
    end
347
  end
348
end