Class: Visor::Image::PutImage

Inherits:
Goliath::API
  • Object
show all
Includes:
Common::Exception, Common::Util
Defined in:
lib/image/routes/put_image.rb

Overview

Put image metadata and/or data for the image with the given id.

Instance Method Summary collapse

Instance Method Details

#do_upload(id, meta, body) ⇒ Array

Upload image file to wanted store.

Parameters:

  • id (Fixnum)

    The image _id.

  • meta (Hash)

    The image metadata.

  • body (FIle)

    The image body tempfile descriptor.

Returns:

  • (Array)

    Image file location URI and size.

Raises:

  • (ArgumentError)

    If request Content-Type isn’t ‘application/octet-stream’



191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/image/routes/put_image.rb', line 191

def do_upload(id, meta, body)
  content_type = env['headers']['Content-Type'] || ''
  store_name   = meta[:store] || configs[:default]
  format       = meta[:format] || 'none'

  unless content_type == 'application/octet-stream'
    raise ArgumentError, 'Request Content-Type must be application/octet-stream'
  end

  store = Visor::Image::Store.get_backend(store_name, configs)
  logger.debug "Uploading image #{id} data to #{store_name} store"
  store.save(id, body, format)
end

#exit_error(code, message, set_status = false) ⇒ Array

Produce an HTTP response with an error code and message.

Parameters:

  • code (Fixnum)

    The error code.

  • message (String)

    The error message.

  • set_status (True, False) (defaults to: false)

    (false) If true, update the image status to ‘error’.

Returns:

  • (Array)

    The HTTP response containing an error code and its message.



125
126
127
128
129
130
131
132
133
# File 'lib/image/routes/put_image.rb', line 125

def exit_error(code, message, set_status=false)
  logger.error message
  begin
    vms.put_image(params[:id], status: 'error') if set_status
  rescue => e
    logger.error "Unable to set image #{env['id']} status to 'error': #{e.message}"
  end
  [code, {}, {code: code, message: message}]
end

#on_body(env, data) ⇒ Object

Pre-process body as it arrives in streaming chunks and load them into a tempfile.

Parameters:

  • env (Object)

    The Goliath environment variables.

  • data (Object)

    The incoming request HTTP body chunks.



29
30
31
32
# File 'lib/image/routes/put_image.rb', line 29

def on_body(env, data)
  (env['body'] ||= Tempfile.open('visor-image', encoding: 'ascii-8bit')) << data
  (env['md5'] ||= Digest::MD5.new) << data
end

#on_close(env) ⇒ Object

On connection close log a message.

Parameters:

  • env (Object)

    The Goliath environment variables.



113
114
115
# File 'lib/image/routes/put_image.rb', line 113

def on_close(env)
  logger.info 'Connection closed'
end

#on_headers(env, headers) ⇒ Object

Pre-process headers as they arrive and load them into a environment variable.

Parameters:

  • env (Object)

    The Goliath environment variables.

  • headers (Object)

    The incoming request HTTP headers.



19
20
21
22
# File 'lib/image/routes/put_image.rb', line 19

def on_headers(env, headers)
  logger.debug "Received headers: #{headers.inspect}"
  env['headers'] = headers
end

#response(env) ⇒ Array

Main response method which processes the received headers and body, managing image metadata and file data.

Parameters:

  • env (Object)

    The Goliath environment variables.

Returns:

  • (Array)

    The HTTP response containing the already inserted image metadata or an error code and its message if anything was raised.



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
# File 'lib/image/routes/put_image.rb', line 42

def response(env)
  begin
    authorize(env, vas)
  rescue Forbidden => e
    return exit_error(403, e.message)
  end

  meta     = pull_meta_from_headers(env['headers'])
  body     = env['body']
  id       = params[:id]
  location = meta[:location]

  # a valid update requires the presence of headers and/or body
  if meta.empty? && body.nil?
    msg = 'No headers or body found for update'
    return exit_error(400, msg)
  end
  # only the x-image-meta-location header or the body content should be provided
  if location && body
    msg = 'When the location header is present no file content can be provided'
    return exit_error(400, msg)
  end

  if meta[:store] == 'http' || (location && location.split(':').first == 'http')
    return exit_error(400, 'Cannot post an image file to a HTTP backend') if body
    store = Visor::Image::Store::HTTP.new(location)

    exist, meta[:size], meta[:checksum] = store.file_exists?(false)
    return exit_error(404, "No image file found at #{location}") unless exist
  end

  # first update the image meta or raises on error
  begin
    image = update_meta(id, meta)
  rescue NotFound => e
    return exit_error(404, e.message)
  rescue ArgumentError => e
    body.close if body
    body.unlink if body
    return exit_error(400, e.message)
  rescue InternalError => e
    body.close if body
    body.unlink if body
    return exit_error(503, e.message)
  end unless meta.empty?

  # if has body(image file), upload file and update meta or raise on error
  begin
    image = upload_and_update(id, body)
  rescue UnsupportedStore, ArgumentError => e
    return exit_error(400, e.message, true)
  rescue NotFound => e
    return exit_error(404, e.message, true)
  rescue ConflictError => e
    return exit_error(409, e.message)
  rescue Duplicated => e
    return exit_error(409, e.message, true)
  rescue InternalError => e
    return exit_error(503, e.message, true)
  ensure
    body.close
    body.unlink
  end unless body.nil?

  [200, {}, {image: image}]
end

#update_meta(id, meta) ⇒ Hash

Update image metadata and set status if needed.

Parameters:

  • meta (Hash)

    The image metadata.

Returns:

  • (Hash)

    The already inserted image metadata.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/image/routes/put_image.rb', line 141

def update_meta(id, meta)
  logger.debug "Updating image #{id} meta:"
  logger.debug "#{id} #{meta}"
  image = vms.put_image(id, meta)
  image.each { |k, v| logger.debug "#{k.to_s.capitalize} setted to '#{v}'" if v }

  if image[:location]
    logger.debug "Location for image #{env['id']} is #{image[:location]}"
    logger.debug "Setting image #{env['id']} status to 'available'"
    vms.put_image(id, status: 'available')
  else
    image
  end
end

#upload_and_update(id, body) ⇒ Hash

Update image status and launch upload.

Parameters:

  • id (Fixnum)

    The image _id.

  • body (FIle)

    The image body tempfile descriptor.

Returns:

  • (Hash)

    The already updated image metadata.

Raises:

  • (ConflictError)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/image/routes/put_image.rb', line 163

def upload_and_update(id, body)
  meta     = vms.get_image(id)
  checksum = env['md5']
  valid    = (meta[:status] == 'locked' || meta[:status] == 'error')
  raise ConflictError, 'Can only assign image file to a locked or error image' unless valid

  logger.debug "Setting image #{id} status to 'uploading'"
  meta           = vms.put_image(id, status: 'uploading')
  location, size = do_upload(id, meta, body)

  logger.debug "Updating image #{id} meta:"
  logger.debug "Setting status to 'available'"
  logger.debug "Setting location to '#{location}'"
  logger.debug "Setting size to '#{size}'"
  logger.debug "Setting checksum to '#{checksum}'"
  vms.put_image(id, status: 'available', location: location, size: size, checksum: checksum)
end