Module: OpenNebula::TemplateExt

Defined in:
lib/opennebula/template_ext.rb

Overview

Module to decorate Template class with additional helpers not directly exposed through the OpenNebula XMLRPC API. The extensions include

- mp_import helper that imports a template into a marketplace

rubocop:disable Style/ClassAndModuleChildren

Class Method Summary collapse

Class Method Details

.extend_object(obj) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/opennebula/template_ext.rb', line 27

def self.extend_object(obj)
    if !obj.is_a?(OpenNebula::Template)
        raise StandardError, "Cannot extended #{obj.class} with TemplateExt"
    end

    class << obj

        ####################################################################
        # Public extended interface
        ####################################################################

        # ------------------------------------------------------------------
        # Imports template into marketplace
        #
        # @param market [Integer] Market to import the Template
        # @param import_all [Bool] true to import images too,
        # @param template_name [String] Virtual Machine Template app name
        #
        # @return [String, Array]
        #   - Error message in case of any or random generated template name
        #   - Objects IDs
        # ------------------------------------------------------------------
        def mp_import(market, import_all, template_name = nil, opts = {})
            opts = {
                :wait => false,
                :logger => nil
            }.merge(opts)

            ids    = []
            images = []
            logger = opts[:logger]

            main_template = ''

            #---------------------------------------------------------------
            # Import all disks as IMAGE
            #---------------------------------------------------------------
            logger.info 'Processing VM disks' if logger

            retrieve_xmlelements('TEMPLATE/DISK').each_with_index do
                |disk, idx|

                image = image_lookup(disk)

                if OpenNebula.is_error?(image)
                    logger.fatal image.message if logger

                    rollback(ids)
                    return [image, ids]
                end

                i_state = OpenNebula::Image::IMAGE_STATES[
                    image['STATE'].to_i
                ]

                unless %w[LOCKED READY USED].include?(i_state)
                    logger.fatal "Wrong image state #{i_state}" if logger

                    rollback(ids)
                    return [image, ids]
                end

                logger.info "Adding disk with image #{image.id}" if logger

                tmpl, main = create_app_template(image, idx)

                if OpenNebula.is_error?(tmpl)
                    logger.fatal tmpl.message if logger

                    rollback(ids)
                    return [tmpl, ids]
                end

                main_template << main

                next unless import_all

                logger.info 'Importing image to market place' if logger

                rc = create_app(tmpl, market)

                if OpenNebula.is_error?(rc)
                    logger.fatal rc.message if logger

                    rollback(ids)
                    return [rc, ids]
                end

                images << image

                ids << rc
            end

            delete_element('TEMPLATE/DISK')

            #---------------------------------------------------------------
            # Import VM template
            #---------------------------------------------------------------
            logger.info 'Processing VM NICs' if logger

            # Replace all nics by auto nics
            nic_xpath   = '/VMTEMPLATE/TEMPLATE/NIC'
            alias_xpath = '/VMTEMPLATE/TEMPLATE/NIC_ALIAS'

            retrieve_xmlelements(nic_xpath).each do |nic|
                if nic['NETWORK']
                    net = "[NETWORK=\"#{nic['NETWORK']}\"]"
                elsif nic['NETWORK_ID']
                    net = "[NETWORK_ID=\"#{nic['NETWORK_ID']}\"]"
                end

                # NIC can be already in auto mode
                next unless net

                # If there are ALIAS the NIC can't be auto,
                # because this combination isn't supported by the core
                nic_alias = retrieve_xmlelements(alias_xpath)

                if !nic_alias || nic_alias.empty?
                    nic.add_element("#{nic_xpath}#{net}",
                                    'NETWORK_MODE' => 'auto')
                else
                    # Add attribute to avoid empty elements
                    nic.add_element("#{nic_xpath}#{net}", 'MARKET' => 'YES')
                end

                delete_nic_attributes(nic)
            end

            retrieve_xmlelements(alias_xpath).each do |nic|
                delete_nic_attributes(nic)
            end

            # Rename it to avoid clashing names
            if template_name
                name = template_name
            else
                name = "#{self['NAME']}-#{SecureRandom.hex[0..9]}"
            end

            main_template << <<-EOT
            NAME      ="#{name}"
            ORIGIN_ID ="-1\"
            TYPE      ="VMTEMPLATE"
            VERSION   ="#{OpenNebula::VERSION}"

            APPTEMPLATE64 ="#{Base64.strict_encode64(template_str)}"
            EOT

            logger.info 'Creating VM app' if logger

            rc = create_app(main_template, market)

            if OpenNebula.is_error?(rc)
                logger.fatal rc.message if logger

                rollback(ids)
                return [rc, ids]
            end

            logger.info 'Waiting for image upload' if logger && opts[:wait]

            images.each {|i| i.wait('READY') } if opts[:wait]

            ids << rc

            [name, ids]
        end

        ####################################################################
        # Private methods
        ####################################################################
        private

        #-------------------------------------------------------------------
        # NIC and NIC Alias attributes to delete when importing template
        # into marketplace.
        #   @param nic [XMLElement] to delete attributes from
        #-------------------------------------------------------------------
        def delete_nic_attributes(nic)
            %w[NETWORK NETWORK_ID NETWORK_UNAME SECURITY_GROUPS].each do |a|
                nic.delete_element(a)
            end
        end

        #-------------------------------------------------------------------
        # Create application template
        #
        # @param id   [Image] OpenNebula Image
        # @param idx  [Integer] Image ID in VM template
        #
        # @return [String, String]
        #   - app template
        #   - content for VM template
        #-------------------------------------------------------------------
        def create_app_template(image, idx = 0)
            i_state = OpenNebula::Image::IMAGE_STATES[image['STATE'].to_i]

            # If the image is used, there is no need to wait until it is
            # ready because the image is already ready to be copied
            if i_state != 'USED' && Integer(image['STATE']) != 1
                # Wait until the image is READY to safe copy it to the MP
                image.wait('READY')
            end

            # Rename to avoid clashing names
            app_name = "#{image['NAME']}-#{SecureRandom.hex[0..9]}"

            dev_prefix = image['TEMPLATE/DEV_PREFIX']
            img_type   = image.type_str

            template64 = "TYPE=#{img_type}\n"
            template64 << "DEV_PREFIX=\"#{dev_prefix}\"\n" if dev_prefix

            template = <<-EOT
            NAME      ="#{app_name}"
            ORIGIN_ID ="#{image['ID']}"
            TYPE      ="IMAGE"
            VERSION   ="#{OpenNebula::VERSION}"

            APPTEMPLATE64 = "#{Base64.strict_encode64(template64)}"
            EOT

            main_template = <<-EOT
            DISK = [
                NAME = "#{img_type}_#{idx}",
                APP  = "#{app_name}"
            ]
            EOT

            [template, main_template]
        end

        #-------------------------------------------------------------------
        # Create application in marketplace
        #
        # @param template  [String]  APP template
        # @param market_id [Integer] Marketplace to import it
        #
        # @return [Integer] APP ID
        #-------------------------------------------------------------------
        def create_app(template, market_id)
            xml = OpenNebula::MarketPlaceApp.build_xml
            app = OpenNebula::MarketPlaceApp.new(xml, @client)

            rc  = app.allocate(template, market_id)

            return rc if OpenNebula.is_error?(rc)

            app.id
        end

        #-------------------------------------------------------------------
        # Delete IDS apps
        #
        # @param ids [Array] Apps IDs to delete
        #-------------------------------------------------------------------
        def rollback(ids)
            ids.each do |id|
                app = OpenNebula::MarketPlaceApp.new_with_id(id, @client)

                app.delete
            end
        end

        #-------------------------------------------------------------------
        # Lookup OpenNebula Images by name or id. Lookup is made on a cached
        # image pool.
        #
        # @param id [Integer, nil] Image id
        # @param name [String] Image name
        #
        # @return [Image]
        #-------------------------------------------------------------------
        def image_lookup(disk)
            # Image pool cache for image id lookup
            if @image_lookup_cache.nil?
                @image_lookup_cache = OpenNebula::ImagePool.new(@client)
                @image_lookup_cache.info
            end

            # Find image information, from ID or NAME. NAME uses the
            # specified user or template owner namespaces
            image = nil

            image_id = disk['IMAGE_ID']

            if image_id
                image = OpenNebula::Image.new_with_id(image_id, @client)
            else
                name  = disk['IMAGE']
                uname = disk['IMAGE_UNAME']
                uname ||= self['UNAME']

                name.gsub!('"', '')
                image = @image_lookup_cache.find do |v|
                    v['NAME'] == name && v['UNAME'] == uname
                end

                return Error.new("Image #{image} not found") if image.nil?
            end

            rc = image.info

            return rc if OpenNebula.is_error? rc

            image.extend(OpenNebula::WaitExt)

            image
        end

    end
end