Project

General

Profile

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

hammer-cli-csv / lib / hammer_cli_csv / content_hosts.rb @ 9e11b4c5

1
# Copyright 2013-2014 Red Hat, Inc.
2
#
3
# This software is licensed to you under the GNU General Public
4
# License as published by the Free Software Foundation; either version
5
# 2 of the License (GPLv2) or (at your option) any later version.
6
# There is NO WARRANTY for this software, express or implied,
7
# including the implied warranties of MERCHANTABILITY,
8
# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
# have received a copy of GPLv2 along with this software; if not, see
10
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11

    
12
require 'hammer_cli'
13
require 'json'
14
require 'csv'
15
require 'uri'
16

    
17
module HammerCLICsv
18
  class CsvCommand
19
    class ContentHostsCommand < BaseCommand
20
      command_name 'content-hosts'
21
      desc         'import or export content hosts'
22

    
23
      option %w(--organization), 'ORGANIZATION', 'Only process organization matching this name'
24

    
25
      ORGANIZATION = 'Organization'
26
      ENVIRONMENT = 'Environment'
27
      CONTENTVIEW = 'Content View'
28
      HOSTCOLLECTIONS = 'Host Collections'
29
      VIRTUAL = 'Virtual'
30
      HOST = 'Host'
31
      OPERATINGSYSTEM = 'OS'
32
      ARCHITECTURE = 'Arch'
33
      SOCKETS = 'Sockets'
34
      RAM = 'RAM'
35
      CORES = 'Cores'
36
      SLA = 'SLA'
37
      PRODUCTS = 'Products'
38
      SUBSCRIPTIONS = 'Subscriptions'
39

    
40
      def export
41
        CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => false}) do |csv|
42
          csv << [NAME, COUNT, ORGANIZATION, ENVIRONMENT, CONTENTVIEW, HOSTCOLLECTIONS, VIRTUAL, HOST,
43
                  OPERATINGSYSTEM, ARCHITECTURE, SOCKETS, RAM, CORES, SLA, PRODUCTS, SUBSCRIPTIONS]
44
          if @server_status['release'] == 'Headpin'
45
            export_sam csv
46
          else
47
            export_foretello csv
48
          end
49
        end
50
      end
51

    
52
      def export_sam(csv)
53
        guests_hypervisor = {}
54
        host_ids = []
55

    
56
        @headpin.get(:organizations).each do |organization|
57
          next if option_organization && organization['name'] != option_organization
58
          host_ids = @headpin.get("organizations/#{organization['label']}/systems").collect do |host|
59
            host['guests'].each { |guest| guests_hypervisor[guest['uuid']] = host['name'] }
60
            host['uuid']
61
          end
62
        end
63

    
64
        host_ids.each do |host_id|
65
          host = @headpin.get("systems/#{host_id}")
66
          host_subscriptions = @headpin.get("systems/#{host_id}/subscriptions")['entitlements']
67

    
68
          name = host['name']
69
          count = 1
70
          organization_name = host['owner']['displayName']
71
          environment = host['environment']['name']
72
          contentview = host['content_view']['name']
73
          hostcollections = nil
74
          virtual = host['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
75
          hypervisor = guests_hypervisor[host['uuid']]
76
          if host['facts']['distribution.name']
77
            operatingsystem = "#{host['facts']['distribution.name']} "
78
            operatingsystem += host['facts']['distribution.version'] if host['facts']['distribution.version']
79
            operatingsystem.strip!
80
          end
81
          architecture = host['facts']['uname.machine']
82
          sockets = host['facts']['cpu.cpu_socket(s)']
83
          ram = host['facts']['memory.memtotal']
84
          cores = host['facts']['cpu.core(s)_per_socket'] || 1
85
          sla = host['serviceLevel']
86

    
87
          products = CSV.generate do |column|
88
            column << host['installedProducts'].collect do |product|
89
              "#{product['productId']}|#{product['productName']}"
90
            end
91
          end
92
          products.delete!("\n")
93

    
94
          subscriptions = CSV.generate do |column|
95
            column << host_subscriptions.collect do |subscription|
96
              "#{subscription['quantity']}|#{subscription['productId']}|#{subscription['poolName']}"
97
            end
98
          end
99
          subscriptions.delete!("\n")
100

    
101
          csv << [name, count, organization_name, environment, contentview, hostcollections, virtual, hypervisor,
102
                  operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
103
        end
104
      end
105

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

    
110
          @api.resource(:systems).call(:index, {
111
              'per_page' => 999999,
112
              'organization_id' => foreman_organization(:name => organization['name'])
113
          })['results'].each do |host|
114
            host = @api.resource(:systems).call(:show, {
115
                'id' => host['uuid'],
116
                'fields' => 'full'
117
            })
118

    
119
            name = host['name']
120
            count = 1
121
            organization_name = organization['name']
122
            environment = host['environment']['label']
123
            contentview = host['content_view']['name']
124
            hostcollections = CSV.generate do |column|
125
              column << host['hostCollections'].collect do |hostcollection|
126
                hostcollection['name']
127
              end
128
            end
129
            hostcollections.delete!("\n")
130
            virtual = host['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
131
            hypervisor_host = host['virtual_host'].nil? ? nil : host['virtual_host']['name']
132
            operatingsystem = "#{host['facts']['distribution.name']} " if host['facts']['distribution.name']
133
            operatingsystem += host['facts']['distribution.version'] if host['facts']['distribution.version']
134
            architecture = host['facts']['uname.machine']
135
            sockets = host['facts']['cpu.cpu_socket(s)']
136
            ram = host['facts']['memory.memtotal']
137
            cores = host['facts']['cpu.core(s)_per_socket'] || 1
138
            sla = ''
139
            products = CSV.generate do |column|
140
              column << host['installedProducts'].collect do |product|
141
                "#{product['productId']}|#{product['productName']}"
142
              end
143
            end
144
            products.delete!("\n")
145
            subscriptions = CSV.generate do |column|
146
              column << @api.resource(:subscriptions).call(:index, {
147
                  'organization_id' => organization['id'],
148
                  'system_id' => host['uuid']
149
              })['results'].collect do |subscription|
150
                "#{subscription['consumed']}|#{subscription['product_id']}|#{subscription['product_name']}"
151
              end
152
            end
153
            subscriptions.delete!("\n")
154
            csv << [name, count, organization_name, environment, contentview, hostcollections, virtual, hypervisor_host,
155
                    operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
156
          end
157
        end
158
      end
159

    
160
      def import
161
        remote = @server_status['plugins'].detect { |plugin| plugin['name'] == 'foreman_csv' }
162
        if remote.nil?
163
          import_locally
164
        else
165
          import_remotely
166
        end
167
      end
168

    
169
      def import_remotely
170
        @api.resource(:csv).call(:import_content_hosts, {
171
            'content' => ::File.new(::File.expand_path(option_csv_file), 'rb')
172
        }, {
173
            :content_type => 'multipart/form-data',
174
            :multipart => true
175
        })
176
      end
177

    
178
      def import_locally
179
        @existing = {}
180
        @hypervisor_guests = {}
181

    
182
        thread_import do |line|
183
          create_content_hosts_from_csv(line)
184
        end
185

    
186
        if !@hypervisor_guests.empty?
187
          print(_('Updating hypervisor and guest associations...')) if option_verbose?
188
          @hypervisor_guests.each do |host_id, guest_ids|
189
            @api.resource(:systems).call(:update, {
190
                'id' => host_id,
191
                'guest_ids' => guest_ids
192
            })
193
          end
194
        end
195
        puts _('done') if option_verbose?
196
      end
197

    
198
      def create_content_hosts_from_csv(line)
199
        return if option_organization && line[ORGANIZATION] != option_organization
200

    
201
        if !@existing[line[ORGANIZATION]]
202
          @existing[line[ORGANIZATION]] = {}
203
          # Fetching all content hosts is too slow and times out due to the complexity of the data
204
          # rendered in the json.
205
          # http://projects.theforeman.org/issues/6307
206
          total = @api.resource(:systems).call(:index, {
207
              'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
208
              'per_page' => 1
209
          })['total'].to_i
210
          (total / 20 + 2).to_i.times do |page|
211
            @api.resource(:systems).call(:index, {
212
                'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
213
                'page' => page,
214
                'per_page' => 20
215
            })['results'].each do |host|
216
              @existing[line[ORGANIZATION]][host['name']] = host['uuid'] if host
217
            end
218
          end
219
        end
220

    
221
        line[COUNT].to_i.times do |number|
222
          name = namify(line[NAME], number)
223

    
224
          if !@existing[line[ORGANIZATION]].include? name
225
            print(_("Creating content host '%{name}'...") % {:name => name}) if option_verbose?
226
            host_id = @api.resource(:systems).call(:create, {
227
                'name' => name,
228
                'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
229
                'environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
230
                'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW]),
231
                'facts' => facts(name, line),
232
                'installed_products' => products(line),
233
                'service_level' => line[SLA],
234
                'type' => 'system'
235
            })['uuid']
236
            @existing[line[ORGANIZATION]][name] = host_id
237
          else
238
            print(_("Updating content host '%{name}'...") % {:name => name}) if option_verbose?
239
            host_id = @api.resource(:systems).call(:update, {
240
                'id' => @existing[line[ORGANIZATION]][name],
241
                'system' => {
242
                    'name' => name,
243
                    'environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
244
                    'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW]),
245
                    'facts' => facts(name, line),
246
                    'installed_products' => products(line)
247
                },
248
                'facts' => facts(name, line),
249
                'installed_products' => products(line),  # TODO: http://projects.theforeman.org/issues/9191,
250
                'service_level' => line[SLA]
251
            })['uuid']
252
          end
253

    
254
          if line[VIRTUAL] == 'Yes' && line[HOST]
255
            raise "Content host '#{line[HOST]}' not found" if !@existing[line[ORGANIZATION]][line[HOST]]
256
            @hypervisor_guests[@existing[line[ORGANIZATION]][line[HOST]]] ||= []
257
            @hypervisor_guests[@existing[line[ORGANIZATION]][line[HOST]]] << "#{line[ORGANIZATION]}/#{name}"
258
          end
259

    
260
          update_host_collections(host_id, line)
261
          update_subscriptions(host_id, line)
262

    
263
          puts _('done') if option_verbose?
264
        end
265
      rescue RuntimeError => e
266
        raise "#{e}\n       #{line}"
267
      end
268

    
269
      private
270

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

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

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

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

    
322
      def update_subscriptions(host_id, line)
323
        existing_subscriptions = @api.resource(:subscriptions).call(:index, {
324
            'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
325
            'per_page' => 999999,
326
            'system_id' => host_id
327
        })['results']
328
        if existing_subscriptions.length > 0
329
          @api.resource(:subscriptions).call(:destroy, {
330
            'system_id' => host_id,
331
            'id' => existing_subscriptions[0]['id']
332
          })
333
        end
334

    
335
        return if line[SUBSCRIPTIONS].nil? || line[SUBSCRIPTIONS].empty?
336

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

    
345
        @api.resource(:subscriptions).call(:create, {
346
            'system_id' => host_id,
347
            'subscriptions' => subscriptions
348
        })
349
      end
350
    end
351
  end
352
end