vmpooler 0.11.3 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ea79e2b20c34ba8ec261bcec65ed74ad0d214fdaaf605b440bfeb6d870016dc
4
- data.tar.gz: 0c880b0c31ab2100aafaf3d6f6b83f50aa723fc44a0dded1a0bfdd0fa6bbc87f
3
+ metadata.gz: fe0ef9bcf3e665cc952fce0975a4381883fca200ef93a883e54c7db7e1fc10e7
4
+ data.tar.gz: d44b61c41a57f940cf0df4bf07aabed10b1a72e91cae5b2105f092a7d98d6d05
5
5
  SHA512:
6
- metadata.gz: 68c5ce2a6a5111824a63c8610c4fd533ac0b538bb29b53064bd09bdd6fbe2b610f3464f5e2270e9de3afbfc1ee6ba0111beed1659e09431c12ebb5124eb305d4
7
- data.tar.gz: 8a69ccbb59c189f8757597bcda0c5ccfe496685582d49992fa604a4fa047c484c424345bff7db60c2830891e48ee8155b4bd0afdc586c7860161f6a8f58da9b8
6
+ metadata.gz: e0e7bbec11ceecc5fdee0fe15b3d37e290a6c884a708a6a5acb2e790d54fb1f1e24ec6966973f02bbecd204a29497e9c17ae3b8be26a0d3f15cec508a0d64532
7
+ data.tar.gz: 7302cf41676a65ff5d0bb4108a8f5ac03a96a92e3cd0ec56deee2f2a60d6e73f09eee569ef0f4140e3c27e81a16c3e2d24b951764a772bdeaa082a96d8bde1fe
@@ -7,6 +7,8 @@ config = Vmpooler.config
7
7
  redis_host = config[:redis]['server']
8
8
  redis_port = config[:redis]['port']
9
9
  redis_password = config[:redis]['password']
10
+ redis_connection_pool_size = config[:redis]['connection_pool_size']
11
+ redis_connection_pool_timeout = config[:redis]['connection_pool_timeout']
10
12
  logger_file = config[:config]['logfile']
11
13
 
12
14
  metrics = Vmpooler.new_metrics(config)
@@ -36,7 +38,7 @@ if torun.include? 'manager'
36
38
  Vmpooler::PoolManager.new(
37
39
  config,
38
40
  Vmpooler.new_logger(logger_file),
39
- Vmpooler.new_redis(redis_host, redis_port, redis_password),
41
+ Vmpooler.redis_connection_pool(redis_host, redis_port, redis_password, redis_connection_pool_size, redis_connection_pool_timeout, metrics),
40
42
  metrics
41
43
  ).execute!
42
44
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vmpooler
4
+ require 'concurrent'
4
5
  require 'date'
5
6
  require 'json'
6
7
  require 'net/ldap'
@@ -58,9 +59,14 @@ module Vmpooler
58
59
 
59
60
  # Set some configuration defaults
60
61
  parsed_config[:config]['task_limit'] = string_to_int(ENV['TASK_LIMIT']) || parsed_config[:config]['task_limit'] || 10
62
+ parsed_config[:config]['ondemand_clone_limit'] = string_to_int(ENV['ONDEMAND_CLONE_LIMIT']) || parsed_config[:config]['ondemand_clone_limit'] || 10
63
+ parsed_config[:config]['max_ondemand_instances_per_request'] = string_to_int(ENV['MAX_ONDEMAND_INSTANCES_PER_REQUEST']) || parsed_config[:config]['max_ondemand_instances_per_request'] || 10
61
64
  parsed_config[:config]['migration_limit'] = string_to_int(ENV['MIGRATION_LIMIT']) if ENV['MIGRATION_LIMIT']
62
65
  parsed_config[:config]['vm_checktime'] = string_to_int(ENV['VM_CHECKTIME']) || parsed_config[:config]['vm_checktime'] || 1
63
66
  parsed_config[:config]['vm_lifetime'] = string_to_int(ENV['VM_LIFETIME']) || parsed_config[:config]['vm_lifetime'] || 24
67
+ parsed_config[:config]['max_lifetime_upper_limit'] = string_to_int(ENV['MAX_LIFETIME_UPPER_LIMIT']) || parsed_config[:config]['max_lifetime_upper_limit']
68
+ parsed_config[:config]['ready_ttl'] = string_to_int(ENV['READY_TTL']) || parsed_config[:config]['ready_ttl'] || 60
69
+ parsed_config[:config]['ondemand_request_ttl'] = string_to_int(ENV['ONDEMAND_REQUEST_TTL']) || parsed_config[:config]['ondemand_request_ttl'] || 5
64
70
  parsed_config[:config]['prefix'] = ENV['PREFIX'] || parsed_config[:config]['prefix'] || ''
65
71
 
66
72
  parsed_config[:config]['logfile'] = ENV['LOGFILE'] if ENV['LOGFILE']
@@ -72,7 +78,7 @@ module Vmpooler
72
78
  parsed_config[:config]['vm_lifetime_auth'] = string_to_int(ENV['VM_LIFETIME_AUTH']) if ENV['VM_LIFETIME_AUTH']
73
79
  parsed_config[:config]['max_tries'] = string_to_int(ENV['MAX_TRIES']) if ENV['MAX_TRIES']
74
80
  parsed_config[:config]['retry_factor'] = string_to_int(ENV['RETRY_FACTOR']) if ENV['RETRY_FACTOR']
75
- parsed_config[:config]['create_folders'] = ENV['CREATE_FOLDERS'] if ENV['CREATE_FOLDERS']
81
+ parsed_config[:config]['create_folders'] = true?(ENV['CREATE_FOLDERS']) if ENV['CREATE_FOLDERS']
76
82
  parsed_config[:config]['create_template_delta_disks'] = ENV['CREATE_TEMPLATE_DELTA_DISKS'] if ENV['CREATE_TEMPLATE_DELTA_DISKS']
77
83
  set_linked_clone(parsed_config)
78
84
  parsed_config[:config]['experimental_features'] = ENV['EXPERIMENTAL_FEATURES'] if ENV['EXPERIMENTAL_FEATURES']
@@ -84,6 +90,8 @@ module Vmpooler
84
90
  parsed_config[:redis]['port'] = string_to_int(ENV['REDIS_PORT']) if ENV['REDIS_PORT']
85
91
  parsed_config[:redis]['password'] = ENV['REDIS_PASSWORD'] if ENV['REDIS_PASSWORD']
86
92
  parsed_config[:redis]['data_ttl'] = string_to_int(ENV['REDIS_DATA_TTL']) || parsed_config[:redis]['data_ttl'] || 168
93
+ parsed_config[:redis]['connection_pool_size'] = string_to_int(ENV['REDIS_CONNECTION_POOL_SIZE']) || parsed_config[:redis]['connection_pool_size'] || 10
94
+ parsed_config[:redis]['connection_pool_timeout'] = string_to_int(ENV['REDIS_CONNECTION_POOL_TIMEOUT']) || parsed_config[:redis]['connection_pool_timeout'] || 5
87
95
 
88
96
  parsed_config[:statsd] = parsed_config[:statsd] || {} if ENV['STATSD_SERVER']
89
97
  parsed_config[:statsd]['server'] = ENV['STATSD_SERVER'] if ENV['STATSD_SERVER']
@@ -117,6 +125,7 @@ module Vmpooler
117
125
 
118
126
  parsed_config[:pools].each do |pool|
119
127
  parsed_config[:pool_names] << pool['name']
128
+ pool['ready_ttl'] ||= parsed_config[:config]['ready_ttl']
120
129
  if pool['alias']
121
130
  if pool['alias'].is_a?(Array)
122
131
  pool['alias'].each do |pool_alias|
@@ -154,6 +163,19 @@ module Vmpooler
154
163
  pools
155
164
  end
156
165
 
166
+ def self.redis_connection_pool(host, port, password, size, timeout, metrics)
167
+ Vmpooler::PoolManager::GenericConnectionPool.new(
168
+ metrics: metrics,
169
+ metric_prefix: 'redis_connection_pool',
170
+ size: size,
171
+ timeout: timeout
172
+ ) do
173
+ connection = Concurrent::Hash.new
174
+ redis = new_redis(host, port, password)
175
+ connection['connection'] = redis
176
+ end
177
+ end
178
+
157
179
  def self.new_redis(host = 'localhost', port = nil, password = nil)
158
180
  Redis.new(host: host, port: port, password: password)
159
181
  end
@@ -84,29 +84,29 @@ module Vmpooler
84
84
  when 'ldap'
85
85
  ldap_base = auth[:ldap]['base']
86
86
  ldap_port = auth[:ldap]['port'] || 389
87
+ ldap_user_obj = auth[:ldap]['user_object']
88
+ ldap_host = auth[:ldap]['host']
87
89
 
88
- if ldap_base.is_a? Array
89
- ldap_base.each do |search_base|
90
+ unless ldap_base.is_a? Array
91
+ ldap_base = ldap_base.split
92
+ end
93
+
94
+ unless ldap_user_obj.is_a? Array
95
+ ldap_user_obj = ldap_user_obj.split
96
+ end
97
+
98
+ ldap_base.each do |search_base|
99
+ ldap_user_obj.each do |search_user_obj|
90
100
  result = authenticate_ldap(
91
101
  ldap_port,
92
- auth[:ldap]['host'],
93
- auth[:ldap]['user_object'],
102
+ ldap_host,
103
+ search_user_obj,
94
104
  search_base,
95
105
  username_str,
96
106
  password_str
97
107
  )
98
- return true if result == true
108
+ return true if result
99
109
  end
100
- else
101
- result = authenticate_ldap(
102
- ldap_port,
103
- auth[:ldap]['host'],
104
- auth[:ldap]['user_object'],
105
- ldap_base,
106
- username_str,
107
- password_str
108
- )
109
- return result
110
110
  end
111
111
 
112
112
  return false
@@ -238,7 +238,7 @@ module Vmpooler
238
238
  queue[:running] = get_total_across_pools_redis_scard(pools, 'vmpooler__running__', backend)
239
239
  queue[:completed] = get_total_across_pools_redis_scard(pools, 'vmpooler__completed__', backend)
240
240
 
241
- queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i
241
+ queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i + backend.get('vmpooler__tasks__ondemandclone').to_i
242
242
  queue[:booting] = queue[:pending].to_i - queue[:cloning].to_i
243
243
  queue[:booting] = 0 if queue[:booting] < 0
244
244
  queue[:total] = queue[:pending].to_i + queue[:ready].to_i + queue[:running].to_i + queue[:completed].to_i
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'vmpooler/util/parsing'
4
+
3
5
  module Vmpooler
4
6
  class API
5
7
  class V1 < Sinatra::Base
@@ -42,6 +44,68 @@ module Vmpooler
42
44
  Vmpooler::API.settings.checkoutlock
43
45
  end
44
46
 
47
+ def get_template_aliases(template)
48
+ result = []
49
+ aliases = Vmpooler::API.settings.config[:alias]
50
+ if aliases
51
+ result += aliases[template] if aliases[template].is_a?(Array)
52
+ template_backends << aliases[template] if aliases[template].is_a?(String)
53
+ end
54
+ result
55
+ end
56
+
57
+ def get_pool_weights(template_backends)
58
+ pool_index = pool_index(pools)
59
+ weighted_pools = {}
60
+ template_backends.each do |t|
61
+ next unless pool_index.key? t
62
+
63
+ index = pool_index[t]
64
+ clone_target = pools[index]['clone_target'] || config['clone_target']
65
+ next unless config.key?('backend_weight')
66
+
67
+ weight = config['backend_weight'][clone_target]
68
+ if weight
69
+ weighted_pools[t] = weight
70
+ end
71
+ end
72
+ weighted_pools
73
+ end
74
+
75
+ def count_selection(selection)
76
+ result = {}
77
+ selection.uniq.each do |poolname|
78
+ result[poolname] = selection.count(poolname)
79
+ end
80
+ result
81
+ end
82
+
83
+ def evaluate_template_aliases(template, count)
84
+ template_backends = []
85
+ template_backends << template if backend.sismember('vmpooler__pools', template)
86
+ selection = []
87
+ aliases = get_template_aliases(template)
88
+ if aliases
89
+ template_backends += aliases
90
+ weighted_pools = get_pool_weights(template_backends)
91
+
92
+ pickup = Pickup.new(weighted_pools) if weighted_pools.count == template_backends.count
93
+ count.to_i.times do
94
+ if pickup
95
+ selection << pickup.pick
96
+ else
97
+ selection << template_backends.sample
98
+ end
99
+ end
100
+ else
101
+ count.to_i.times do
102
+ selection << template
103
+ end
104
+ end
105
+
106
+ count_selection(selection)
107
+ end
108
+
45
109
  def fetch_single_vm(template)
46
110
  template_backends = [template]
47
111
  aliases = Vmpooler::API.settings.config[:alias]
@@ -245,11 +309,9 @@ module Vmpooler
245
309
  pool_index = pool_index(pools)
246
310
  template_configs = backend.hgetall('vmpooler__config__template')
247
311
  template_configs&.each do |poolname, template|
248
- if pool_index.include? poolname
249
- unless pools[pool_index[poolname]]['template'] == template
250
- pools[pool_index[poolname]]['template'] = template
251
- end
252
- end
312
+ next unless pool_index.include? poolname
313
+
314
+ pools[pool_index[poolname]]['template'] = template
253
315
  end
254
316
  end
255
317
 
@@ -257,11 +319,9 @@ module Vmpooler
257
319
  pool_index = pool_index(pools)
258
320
  poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
259
321
  poolsize_configs&.each do |poolname, size|
260
- if pool_index.include? poolname
261
- unless pools[pool_index[poolname]]['size'] == size.to_i
262
- pools[pool_index[poolname]]['size'] == size.to_i
263
- end
264
- end
322
+ next unless pool_index.include? poolname
323
+
324
+ pools[pool_index[poolname]]['size'] = size.to_i
265
325
  end
266
326
  end
267
327
 
@@ -269,12 +329,70 @@ module Vmpooler
269
329
  pool_index = pool_index(pools)
270
330
  clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
271
331
  clone_target_configs&.each do |poolname, clone_target|
272
- if pool_index.include? poolname
273
- unless pools[pool_index[poolname]]['clone_target'] == clone_target
274
- pools[pool_index[poolname]]['clone_target'] == clone_target
275
- end
276
- end
332
+ next unless pool_index.include? poolname
333
+
334
+ pools[pool_index[poolname]]['clone_target'] = clone_target
335
+ end
336
+ end
337
+
338
+ def too_many_requested?(payload)
339
+ payload&.each do |poolname, count|
340
+ next unless count.to_i > config['max_ondemand_instances_per_request']
341
+
342
+ metrics.increment('ondemandrequest.toomanyrequests.' + poolname)
343
+ return true
344
+ end
345
+ false
346
+ end
347
+
348
+ def generate_ondemand_request(payload)
349
+ result = { 'ok': false }
350
+
351
+ requested_instances = payload.reject { |k, _v| k == 'request_id' }
352
+ if too_many_requested?(requested_instances)
353
+ result['message'] = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
354
+ status 403
355
+ return result
356
+ end
357
+
358
+ score = Time.now.to_i
359
+ request_id = payload['request_id']
360
+ request_id ||= generate_request_id
361
+ result['request_id'] = request_id
362
+
363
+ if backend.exists("vmpooler__odrequest__#{request_id}")
364
+ result['message'] = "request_id '#{request_id}' has already been created"
365
+ status 409
366
+ metrics.increment('ondemandrequest.generate.duplicaterequests')
367
+ return result
368
+ end
369
+
370
+ status 201
371
+
372
+ platforms_with_aliases = []
373
+ requested_instances.each do |poolname, count|
374
+ selection = evaluate_template_aliases(poolname, count)
375
+ selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
376
+ end
377
+ platforms_string = platforms_with_aliases.join(',')
378
+
379
+ return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
380
+
381
+ backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
382
+ if Vmpooler::API.settings.config[:auth] and has_token?
383
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
384
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:user',
385
+ backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user'))
277
386
  end
387
+
388
+ result['domain'] = config['domain'] if config['domain']
389
+ result[:ok] = true
390
+ metrics.increment('ondemandrequest.generate.success')
391
+ result
392
+ end
393
+
394
+ def generate_request_id
395
+ SecureRandom.uuid
278
396
  end
279
397
 
280
398
  get '/' do
@@ -395,7 +513,7 @@ module Vmpooler
395
513
  end
396
514
 
397
515
  # for backwards compatibility, include separate "empty" stats in "status" block
398
- if ready == 0
516
+ if ready == 0 && max != 0
399
517
  result[:status][:empty] ||= []
400
518
  result[:status][:empty].push(pool['name'])
401
519
 
@@ -689,6 +807,88 @@ module Vmpooler
689
807
  JSON.pretty_generate(result)
690
808
  end
691
809
 
810
+ post "#{api_prefix}/ondemandvm/?" do
811
+ content_type :json
812
+
813
+ need_token! if Vmpooler::API.settings.config[:auth]
814
+
815
+ result = { 'ok' => false }
816
+
817
+ begin
818
+ payload = JSON.parse(request.body.read)
819
+
820
+ if payload
821
+ invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
822
+ if invalid.empty?
823
+ result = generate_ondemand_request(payload)
824
+ else
825
+ result[:bad_templates] = invalid
826
+ invalid.each do |bad_template|
827
+ metrics.increment('ondemandrequest.invalid.' + bad_template)
828
+ end
829
+ status 404
830
+ end
831
+ else
832
+ metrics.increment('ondemandrequest.invalid.unknown')
833
+ status 404
834
+ end
835
+ rescue JSON::ParserError
836
+ status 400
837
+ result = {
838
+ 'ok' => false,
839
+ 'message' => 'JSON payload could not be parsed'
840
+ }
841
+ end
842
+
843
+ JSON.pretty_generate(result)
844
+ end
845
+
846
+ post "#{api_prefix}/ondemandvm/:template/?" do
847
+ content_type :json
848
+ result = { 'ok' => false }
849
+
850
+ need_token! if Vmpooler::API.settings.config[:auth]
851
+
852
+ payload = extract_templates_from_query_params(params[:template])
853
+
854
+ if payload
855
+ invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
856
+ if invalid.empty?
857
+ result = generate_ondemand_request(payload)
858
+ else
859
+ result[:bad_templates] = invalid
860
+ invalid.each do |bad_template|
861
+ metrics.increment('ondemandrequest.invalid.' + bad_template)
862
+ end
863
+ status 404
864
+ end
865
+ else
866
+ metrics.increment('ondemandrequest.invalid.unknown')
867
+ status 404
868
+ end
869
+
870
+ JSON.pretty_generate(result)
871
+ end
872
+
873
+ get "#{api_prefix}/ondemandvm/:requestid/?" do
874
+ content_type :json
875
+
876
+ status 404
877
+ result = check_ondemand_request(params[:requestid])
878
+
879
+ JSON.pretty_generate(result)
880
+ end
881
+
882
+ delete "#{api_prefix}/ondemandvm/:requestid/?" do
883
+ content_type :json
884
+ need_token! if Vmpooler::API.settings.config[:auth]
885
+
886
+ status 404
887
+ result = delete_ondemand_request(params[:requestid])
888
+
889
+ JSON.pretty_generate(result)
890
+ end
891
+
692
892
  post "#{api_prefix}/vm/?" do
693
893
  content_type :json
694
894
  result = { 'ok' => false }
@@ -764,6 +964,73 @@ module Vmpooler
764
964
  invalid
765
965
  end
766
966
 
967
+ def check_ondemand_request(request_id)
968
+ result = { 'ok' => false }
969
+ request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
970
+ if request_hash.empty?
971
+ result['message'] = "no request found for request_id '#{request_id}'"
972
+ return result
973
+ end
974
+
975
+ result['request_id'] = request_id
976
+ result['ready'] = false
977
+ result['ok'] = true
978
+ status 202
979
+
980
+ if request_hash['status'] == 'ready'
981
+ result['ready'] = true
982
+ Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
983
+ instances = backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
984
+ result[platform_alias] = { 'hostname': instances }
985
+ end
986
+ result['domain'] = config['domain'] if config['domain']
987
+ status 200
988
+ elsif request_hash['status'] == 'failed'
989
+ result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
990
+ status 200
991
+ elsif request_hash['status'] == 'deleted'
992
+ result['message'] = 'The request has been deleted'
993
+ status 200
994
+ else
995
+ Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, count|
996
+ instance_count = backend.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
997
+ result[platform_alias] = {
998
+ 'ready': instance_count.to_s,
999
+ 'pending': (count.to_i - instance_count.to_i).to_s
1000
+ }
1001
+ end
1002
+ end
1003
+
1004
+ result
1005
+ end
1006
+
1007
+ def delete_ondemand_request(request_id)
1008
+ result = { 'ok' => false }
1009
+
1010
+ platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
1011
+ unless platforms
1012
+ result['message'] = "no request found for request_id '#{request_id}'"
1013
+ return result
1014
+ end
1015
+
1016
+ if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
1017
+ result['message'] = 'the request has already been deleted'
1018
+ else
1019
+ backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
1020
+
1021
+ Parsing.get_platform_pool_count(platforms) do |platform_alias, pool, _count|
1022
+ backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")&.each do |vm|
1023
+ backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
1024
+ end
1025
+ backend.del("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1026
+ end
1027
+ backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
1028
+ end
1029
+ status 200
1030
+ result['ok'] = true
1031
+ result
1032
+ end
1033
+
767
1034
  post "#{api_prefix}/vm/:template/?" do
768
1035
  content_type :json
769
1036
  result = { 'ok' => false }
@@ -923,6 +1190,7 @@ module Vmpooler
923
1190
  unless arg.to_i > 0
924
1191
  failure.push("You provided a lifetime (#{arg}) but you must provide a positive number.")
925
1192
  end
1193
+
926
1194
  when 'tags'
927
1195
  unless arg.is_a?(Hash)
928
1196
  failure.push("You provided tags (#{arg}) as something other than a hash.")
@@ -1047,7 +1315,7 @@ module Vmpooler
1047
1315
  invalid.each do |bad_template|
1048
1316
  metrics.increment("config.invalid.#{bad_template}")
1049
1317
  end
1050
- result[:bad_templates] = invalid
1318
+ result[:not_configured] = invalid
1051
1319
  status 400
1052
1320
  end
1053
1321
  else