Project

General

Profile

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

hammer-cli-csv / lib / hammer_cli_csv / content_hosts.rb @ 561a8ac9

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
#
13
# -= Systems CSV =-
14
#
15
# Columns
16
#   Name
17
#     - System name
18
#     - May contain '%d' which will be replaced with current iteration number of Count
19
#     - eg. "os%d" -> "os1"
20
#   Count
21
#     - Number of times to iterate on this line of the CSV file
22
#   MAC Address
23
#     - MAC address
24
#     - May contain '%d' which will be replaced with current iteration number of Count
25
#     - eg. "FF:FF:FF:FF:FF:%02x" -> "FF:FF:FF:FF:FF:0A"
26
#     - Warning: be sure to keep count below 255 or MAC hex will exceed limit
27
#
28

    
29
require 'hammer_cli'
30
require 'json'
31
require 'csv'
32
require 'uri'
33

    
34
module HammerCLICsv
35
  class CsvCommand
36
    class ContentHostsCommand < BaseCommand
37
      command_name 'content-hosts'
38
      desc         'import or export content hosts'
39

    
40
      option %w(--sam), :flag, 'export from SAM-1.3 or SAM-1.4'
41

    
42
      ORGANIZATION = 'Organization'
43
      ENVIRONMENT = 'Environment'
44
      CONTENTVIEW = 'Content View'
45
      HOSTCOLLECTIONS = 'Host Collections'
46
      VIRTUAL = 'Virtual'
47
      HOST = 'Host'
48
      OPERATINGSYSTEM = 'OS'
49
      ARCHITECTURE = 'Arch'
50
      SOCKETS = 'Sockets'
51
      RAM = 'RAM'
52
      CORES = 'Cores'
53
      SLA = 'SLA'
54
      PRODUCTS = 'Products'
55
      SUBSCRIPTIONS = 'Subscriptions'
56

    
57
      def export
58
        CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => false}) do |csv|
59
          csv << [NAME, COUNT, ORGANIZATION, ENVIRONMENT, CONTENTVIEW, HOSTCOLLECTIONS, VIRTUAL, HOST,
60
                  OPERATINGSYSTEM, ARCHITECTURE, SOCKETS, RAM, CORES, SLA, PRODUCTS, SUBSCRIPTIONS]
61
          if option_sam?
62
            export_sam csv
63
          else
64
            export_foretello csv
65
          end
66
        end
67
      end
68

    
69
      def export_sam(csv)
70
        guests_host = {}
71

    
72
        server = option_server || HammerCLI::Settings.get(:csv, :host)
73
        username = option_username || HammerCLI::Settings.get(:csv, :username)
74
        password = option_password || HammerCLI::Settings.get(:csv, :password)
75
        url = "#{server}/api/systems?organization_id=satellite-1"
76
        uri = URI(url)
77
        system_ids = Net::HTTP.start(uri.host, uri.port,
78
                                     :use_ssl => uri.scheme == 'https',
79
                                     :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
80
          request = Net::HTTP::Get.new uri.request_uri
81
          request.basic_auth(username, password)
82
          response = http.request(request)
83

    
84
          JSON.parse(response.body).collect do |system|
85
            system['guests'].each { |guest| guests_host[guest['uuid']] = system['name'] }
86
            system['uuid']
87
          end
88
        end
89

    
90
        #system_ids = ['327bf5cc-5498-4927-aa27-39b1503edf58', 'eeb6de95-afaf-4b14-9f92-e845ff269ccb']
91

    
92
        system_ids.each do |system_id|
93
          url = "#{server}/api/systems/#{system_id}"
94
          uri = URI(url)
95
          system = Net::HTTP.start(uri.host, uri.port,
96
                                  :use_ssl => uri.scheme == 'https',
97
                                  :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
98
            request = Net::HTTP::Get.new uri.request_uri
99
            request.basic_auth(username, password)
100
            response = http.request(request)
101

    
102
            JSON.parse(response.body)
103
          end
104

    
105
          url = "#{server}/api/systems/#{system_id}/subscriptions"
106
          uri = URI(url)
107
          system_subscriptions = Net::HTTP.start(uri.host, uri.port,
108
                                                 :use_ssl => uri.scheme == 'https',
109
                                                 :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
110
            request = Net::HTTP::Get.new uri.request_uri
111
            request.basic_auth(username, password)
112
            response = http.request(request)
113

    
114
            JSON.parse(response.body)['entitlements']
115
          end
116

    
117
          name = system['name']
118
          count = 1
119
          organization_name = system['owner']['displayName']
120
          environment = system['environment']['name']
121
          contentview = system['content_view']['name']
122
          hostcollections = nil
123
          virtual = system['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
124
          host = guests_host[system['uuid']]
125
          if system['facts']['distribution.name']
126
            operatingsystem = "#{system['facts']['distribution.name']} "
127
            operatingsystem += system['facts']['distribution.version'] if system['facts']['distribution.version']
128
            operatingsystem.strip!
129
          end
130
          architecture = system['facts']['uname.machine']
131
          sockets = system['facts']['cpu.cpu_socket(s)']
132
          ram = system['facts']['memory.memtotal']
133
          cores = system['facts']['cpu.core(s)_per_socket'] || 1
134
          sla = system['serviceLevel']
135

    
136
          products = CSV.generate do |column|
137
            column << system['installedProducts'].collect do |product|
138
              "#{product['productId']}|#{product['productName']}"
139
            end
140
          end
141
          products.delete!("\n")
142

    
143
          subscriptions = CSV.generate do |column|
144
            column << system_subscriptions.collect do |subscription|
145
              "#{subscription['quantity']}|#{subscription['productId']}|#{subscription['poolName']}"
146
            end
147
          end
148
          subscriptions.delete!("\n")
149

    
150
          csv << [name, count, organization_name, environment, contentview, hostcollections, virtual, host,
151
                  operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
152
        end
153
      end
154

    
155
      def export_foretello(csv)
156
        @api.resource(:organizations)\
157
          .call(:index, {:per_page => 999999})['results'].each do |organization|
158
          @api.resource(:systems)\
159
            .call(:index, {
160
                    'per_page' => 999999,
161
                    'organization_id' => foreman_organization(:name => organization['name'])
162
                  })['results'].each do |system|
163
            system = @api.resource(:systems)\
164
              .call(:show, {
165
                      'id' => system['uuid'],
166
                      'fields' => 'full'
167
                    })
168

    
169
            name = system['name']
170
            count = 1
171
            organization_name = organization['name']
172
            environment = system['environment']['label']
173
            contentview = system['content_view']['name']
174
            hostcollections = CSV.generate do |column|
175
              column << system['systemGroups'].collect do |hostcollection|
176
                hostcollection['name']
177
              end
178
            end
179
            hostcollections.delete!("\n")
180
            virtual = system['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
181
            host = system['virtual_host']
182
            operatingsystem = "#{system['facts']['distribution.name']} " if system['facts']['distribution.name']
183
            operatingsystem += system['facts']['distribution.version'] if system['facts']['distribution.version']
184
            architecture = system['facts']['uname.machine']
185
            sockets = system['facts']['cpu.cpu_socket(s)']
186
            ram = system['facts']['memory.memtotal']
187
            cores = system['facts']['cpu.core(s)_per_socket'] || 1
188
            sla = ''
189
            products = CSV.generate do |column|
190
              column << system['installedProducts'].collect do |product|
191
                "#{product['productId']}|#{product['productName']}"
192
              end
193
            end
194
            products.delete!("\n")
195
            subscriptions = CSV.generate do |column|
196
              column << @api.resource(:subscriptions)\
197
                .call(:index, {
198
                        'system_id' => system['uuid']
199
                      })['results'].collect do |subscription|
200
                "#{subscription['quantity']}|#{subscription['product_id']}|#{subscription['product_name']}"
201
              end
202
            end
203
            subscriptions.delete!("\n")
204
            csv << [name, count, organization_name, environment, contentview, hostcollections, virtual, host,
205
                    operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
206
          end
207
        end
208
      end
209

    
210
      def import
211
        @existing = {}
212
        @host_guests = {}
213

    
214
        thread_import do |line|
215
          create_systems_from_csv(line)
216
        end
217

    
218
        print 'Updating host and guest associations...' if option_verbose?
219
        @host_guests.each do |host_id, guest_ids|
220
          @api.resource(:systems)\
221
            .call(:update, {
222
                    'id' => host_id,
223
                    'guest_ids' => guest_ids
224
                  })
225
        end
226
        puts 'done' if option_verbose?
227
      end
228

    
229
      def create_systems_from_csv(line)
230
        if !@existing[line[ORGANIZATION]]
231
          @existing[line[ORGANIZATION]] = {}
232
          # Fetching all content hosts is too slow and times out due to the complexity of the data
233
          # rendered in the json.
234
          # http://projects.theforeman.org/issues/6307
235
          total = @api.resource(:systems)\
236
            .call(:index, {
237
                    'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
238
                    'per_page' => 1
239
                  })['total'].to_i
240
          (total / 20 + 2).to_i.times do |page|
241
            @api.resource(:systems)\
242
              .call(:index, {
243
                      'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
244
                      'page' => page,
245
                      'per_page' => 20
246
                    })['results'].each do |system|
247
              @existing[line[ORGANIZATION]][system['name']] = system['uuid'] if system
248
            end
249
          end
250
        end
251

    
252
        line[COUNT].to_i.times do |number|
253
          name = namify(line[NAME], number)
254

    
255
          # TODO: w/ @daviddavis p-r
256
          #subscriptions(line).each do |subscription|
257
          #  katello_subscription(line[ORGANIZATION], :name => subscription[:number])
258
          #end
259

    
260
          if !@existing[line[ORGANIZATION]].include? name
261
            print "Creating system '#{name}'..." if option_verbose?
262
            system_id = @api.resource(:systems)\
263
              .call(:create, {
264
                      'name' => name,
265
                      'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
266
                      'environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
267
                      'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW]),
268
                      'facts' => facts(name, line),
269
                      'installed_products' => products(line)
270
                    })['uuid']
271
            @existing[line[ORGANIZATION]][name] = system_id
272
          else
273
            print "Updating system '#{name}'..." if option_verbose?
274
            system_id = @api.resource(:systems)\
275
              .call(:update, {
276
                      'id' => @existing[line[ORGANIZATION]][name],
277
                      'system' => {
278
                        'name' => name,
279
                        'environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
280
                        'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW]),
281
                        'facts' => facts(name, line),
282
                        'installed_products' => products(line)
283
                      }
284
                    })['uuid']
285
          end
286

    
287
          if line[VIRTUAL] == 'Yes' && line[HOST]
288
            raise "Host system '#{line[HOST]}' not found" if !@existing[line[ORGANIZATION]][line[HOST]]
289
            @host_guests[@existing[line[ORGANIZATION]][line[HOST]]] ||= []
290
            @host_guests[@existing[line[ORGANIZATION]][line[HOST]]] << "#{line[ORGANIZATION]}/#{name}"
291
          end
292

    
293
          set_host_collections(system_id, line)
294

    
295
          puts 'done' if option_verbose?
296
        end
297
      rescue RuntimeError => e
298
        raise "#{e}\n       #{line}"
299
      end
300

    
301
      private
302

    
303
      def facts(name, line)
304
        facts = {}
305
        facts['network.hostname'] = name
306
        facts['cpu.core(s)_per_socket'] = line[CORES]
307
        facts['cpu.cpu_socket(s)'] = line[SOCKETS]
308
        facts['memory.memtotal'] = line[RAM]
309
        facts['uname.machine'] = line[ARCHITECTURE]
310
        if line[OPERATINGSYSTEM].nil?
311
          facts['distribution.name'] = nil
312
          facts['distribution.version'] = nil
313
        elsif line[OPERATINGSYSTEM].index(' ')
314
          (facts['distribution.name'], facts['distribution.version']) = line[OPERATINGSYSTEM].split(' ')
315
        else
316
          (facts['distribution.name'], facts['distribution.version']) = ['RHEL', line[OPERATINGSYSTEM]]
317
        end
318
        facts['virt.is_guest'] = line[VIRTUAL] == 'Yes' ? true : false
319
        facts['virt.uuid'] = "#{line[ORGANIZATION]}/#{name}" if facts['virt.is_guest']
320
        facts
321
      end
322

    
323
      def set_host_collections(system_id, line)
324
        return nil if !line[HOSTCOLLECTIONS]
325
        CSV.parse_line(line[HOSTCOLLECTIONS]).each do |hostcollection_name|
326
          @api.resource(:host_collections)\
327
            .call(:add_systems, {
328
                    'id' => katello_hostcollection(line[ORGANIZATION], :name => hostcollection_name),
329
                    'system_ids' => [system_id]
330
                  })
331
        end
332
      end
333

    
334
      def products(line)
335
        return nil if !line[PRODUCTS]
336
        products = CSV.parse_line(line[PRODUCTS]).collect do |product_details|
337
          product = {}
338
          # TODO: these get passed straight through to candlepin; probably would be better to process in server
339
          #       to allow underscore product_id here
340
          (product['productId'], product['productName']) = product_details.split('|')
341
          product
342
        end
343
        products
344
      end
345

    
346
      def subscriptions(line)
347
        return nil if !line[SUBSCRIPTIONS]
348
        subscriptions = CSV.parse_line(line[SUBSCRIPTIONS]).collect do |subscription_details|
349
          subscription = {}
350
          (subscription[:quantity], subscription[:number], subscription[:name]) = subscription_details.split('|')
351
          subscription
352
        end
353
        subscriptions
354
      end
355
    end
356
  end
357
end