Class: OodCore::Job::Adapters::Coder::Batch

Inherits:
Object
  • Object
show all
Defined in:
lib/ood_core/job/adapters/coder/batch.rb

Overview

Utility class for the Coder adapter to interact with the Coders API.

Defined Under Namespace

Classes: Error

Instance Method Summary collapse

Constructor Details

#initialize(config, credentials) ⇒ Batch

Returns a new instance of Batch.



9
10
11
12
13
14
15
16
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 9

def initialize(config, credentials)
  @host = config[:host]
  @token = config[:token]
  @service_user = config[:service_user]
  @credential_deletion_max_attempts = config[:credential_deletion_max_attempts] || 5
  @credential_deletion_timeout_interval = config[:credential_deletion_timeout_interval] || 10
  @credentials = credentials 
end

Instance Method Details

#api_call(method, endpoint, headers, body = nil) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 162

def api_call(method, endpoint, headers, body = nil)
  uri = URI(endpoint)
  case method.downcase
  when 'get'
    request = Net::HTTP::Get.new(uri, headers)
  when 'post'
    request = Net::HTTP::Post.new(uri, headers)
  when 'delete'
    request = Net::HTTP::Delete.new(uri, headers)
  else
    raise ArgumentError, "Invalid HTTP method: #{method}"
  end

  request.body = body.to_json if body
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
    http.request(request)
  end
  case response
  when Net::HTTPSuccess
    JSON.parse(response.body)
  else
    raise Error, "HTTP Error: #{response.code} #{response.message}  for request #{endpoint} and body #{body}"
  end
end

#build_coder_job_info(json_data, status) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 117

def build_coder_job_info(json_data, status)
   = json_data["latest_build"]["resources"]
  &.find { |resource| resource["name"] == "coder_output" }
  &.dig("metadata")
  coder_output_hash = &.map { |meta| [meta["key"].to_sym, meta["value"]] }&.to_h || {}
  OodCore::Job::Adapters::Coder::CoderJobInfo.new(**{
    id: json_data["id"],
    job_name: json_data["workspace_name"],
    status: OodCore::Job::Status.new(state: status),
    job_owner: json_data["workspace_owner_name"],
    submission_time: json_data["created_at"],
    dispatch_time: json_data.dig("updated_at"),
    wallclock_time: wallclock_time(json_data, status),
    ood_connection_info: { host: coder_output_hash[:floating_ip], port: 80 },
    native: coder_output_hash
})
end

#coder_state_to_ood_status(coder_state) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 100

def coder_state_to_ood_status(coder_state)
  case coder_state
  when "starting"
    "queued"
  when "failed"
    "suspended"
  when "running"
    "running"
  when "deleted"
    "completed"
  when "stopped"
    "completed"
  else
    "undetermined"
  end
end

#delete(id) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 61

def delete(id)
  endpoint = "#{@host}/api/v2/workspaces/#{id}/builds"
  headers = get_headers(@token)
  body = {
    'orphan' => false,
    'transition' => 'delete'
  }
  api_call('post', endpoint, headers, body)

  credentials = @credentials.load_credentials(id, username)

  wait_for_workspace_deletion(id) do |attempt|
    puts "#{Time.now.inspect} Deleting workspace (attempt #{attempt + 1}/#{5})"
  end

  @credentials.destroy_credentials(credentials, workspace_json(id).dig("latest_build", "status"), id, username)
end

#end_time(json_data, status) ⇒ Object



146
147
148
149
150
151
152
153
154
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 146

def end_time(json_data, status)
  if status == 'deleted'
    end_time_string = json_data["latest_build"].dig("updated_at") 
    et = DateTime.parse(end_time_string).to_time.to_i
  else
    et = DateTime.now.to_time.to_i
  end
  et
end

#get_headers(coder_token) ⇒ Object



33
34
35
36
37
38
39
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 33

def get_headers(coder_token)
  {
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
    'Coder-Session-Token' => coder_token
  }
end

#get_rich_parameters(coder_parameters, project_id, app_credentials) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 18

def get_rich_parameters(coder_parameters, project_id, app_credentials)
  rich_parameter_values = [
    { name: "application_credential_name", value: app_credentials[:name] },
    { name: "application_credential_id", value: app_credentials[:id] },
    { name: "application_credential_secret", value: app_credentials[:secret] },
    {name: "project_id", value: project_id }
  ]
  if coder_parameters
    coder_parameters.each do |key, value|
      rich_parameter_values << { name: key, value: value.to_s}
    end
  end
  rich_parameter_values
end

#info(id) ⇒ Object



96
97
98
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 96

def info(id)
  workspace_info_from_json(workspace_json(id))
end

#start_time(json_data) ⇒ Object



141
142
143
144
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 141

def start_time(json_data)
  start_time_string = json_data.dig("updated_at")
  DateTime.parse(start_time_string).to_time.to_i
end

#submit(script) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 41

def submit(script)
  org_id = script.native[:org_id]
  project_id = script.native[:project_id]
  coder_parameters = script.native[:coder_parameters]
  endpoint = "#{@host}/api/v2/organizations/#{org_id}/members/#{@service_user}/workspaces"
  app_credentials = @credentials.generate_credentials(project_id, username)
  headers = get_headers(@token)
  workspace_name = "#{username}-#{script.native[:workspace_name]}-#{rand(2_821_109_907_456).to_s(36)}"
  body = {
    template_version_id: script.native[:template_version_id],
    name: workspace_name,
    rich_parameter_values: get_rich_parameters(coder_parameters, project_id, app_credentials),
  }

  resp = api_call('post', endpoint, headers, body)
  @credentials.save_credentials(resp["id"], username, app_credentials)
  resp["id"]

end

#usernameObject



187
188
189
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 187

def username
  @username ||= Etc.getlogin
end

#wait_for_workspace_deletion(id) ⇒ Object



79
80
81
82
83
84
85
86
87
88
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 79

def wait_for_workspace_deletion(id)
  max_attempts = @credential_deletion_max_attempts
  timeout_interval = @credential_deletion_timeout_interval

  max_attempts.times do |attempt|
    break unless workspace_json(id) && workspace_json(id).dig("latest_build", "status") == "deleting"
    yield(attempt + 1)
    sleep(timeout_interval)
  end
end

#wallclock_time(json_data, status) ⇒ Object



135
136
137
138
139
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 135

def wallclock_time(json_data, status)
  start_time = start_time(json_data) 
  end_time = end_time(json_data, status)
  end_time - start_time
end

#workspace_info_from_json(json_data) ⇒ Object



156
157
158
159
160
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 156

def workspace_info_from_json(json_data)
  state = json_data.dig("latest_build", "status") || json_data.dig("latest_build", "job", "status")
  status = coder_state_to_ood_status(state)
  build_coder_job_info(json_data, status)
end

#workspace_json(id) ⇒ Object



90
91
92
93
94
# File 'lib/ood_core/job/adapters/coder/batch.rb', line 90

def workspace_json(id)
  endpoint = "#{@host}/api/v2/workspaces/#{id}?include_deleted=true"
  headers = get_headers(@token)
  api_call('get', endpoint, headers)
end