You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
4.7 KiB

  1. #!/usr/bin/env python
  2. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  3. # not use this file except in compliance with the License. You may obtain
  4. # a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software
  9. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  10. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  11. # License for the specific language governing permissions and limitations
  12. # under the License.
  13. # based on http://code.activestate.com/recipes/577120-julia-fractals/
  14. import base64
  15. import copy
  16. import hashlib
  17. import json
  18. import os
  19. from PIL import Image
  20. import random
  21. import socket
  22. import tempfile
  23. import time
  24. from kombu.mixins import ConsumerMixin
  25. from oslo_config import cfg
  26. from oslo_log import log
  27. import requests
  28. from faafo import queues
  29. LOG = log.getLogger('faafo.worker')
  30. CONF = cfg.CONF
  31. worker_opts = {
  32. cfg.StrOpt('endpoint-url',
  33. default='http://localhost',
  34. help='API connection URL')
  35. }
  36. CONF.register_opts(worker_opts)
  37. def list_opts():
  38. """Entry point for oslo-config-generator."""
  39. return [(None, copy.deepcopy(worker_opts))]
  40. class JuliaSet(object):
  41. def __init__(self, width, height, xa=-2.0, xb=2.0, ya=-1.5, yb=1.5,
  42. iterations=255):
  43. self.xa = xa
  44. self.xb = xb
  45. self.ya = ya
  46. self.yb = yb
  47. self.iterations = iterations
  48. self.width = width
  49. self.height = height
  50. self.draw()
  51. def draw(self):
  52. self.image = Image.new("RGB", (self.width, self.height))
  53. c, z = self._set_point()
  54. for y in range(self.height):
  55. zy = y * (self.yb - self.ya) / (self.height - 1) + self.ya
  56. for x in range(self.width):
  57. zx = x * (self.xb - self.xa) / (self.width - 1) + self.xa
  58. z = zx + zy * 1j
  59. for i in range(self.iterations):
  60. if abs(z) > 2.0:
  61. break
  62. z = z * z + c
  63. self.image.putpixel((x, y),
  64. (i % 8 * 32, i % 16 * 16, i % 32 * 8))
  65. def get_file(self):
  66. with tempfile.NamedTemporaryFile(delete=False) as fp:
  67. self.image.save(fp, "PNG")
  68. return fp.name
  69. def _set_point(self):
  70. random.seed()
  71. while True:
  72. cx = random.random() * (self.xb - self.xa) + self.xa
  73. cy = random.random() * (self.yb - self.ya) + self.ya
  74. c = cx + cy * 1j
  75. z = c
  76. for i in range(self.iterations):
  77. if abs(z) > 2.0:
  78. break
  79. z = z * z + c
  80. if i > 10 and i < 100:
  81. break
  82. return (c, z)
  83. class Worker(ConsumerMixin):
  84. def __init__(self, connection):
  85. self.connection = connection
  86. def get_consumers(self, Consumer, channel):
  87. return [Consumer(queues=queues.task_queue,
  88. accept=['json'],
  89. callbacks=[self.process])]
  90. def process(self, task, message):
  91. LOG.info("processing task %s" % task['uuid'])
  92. LOG.debug(task)
  93. start_time = time.time()
  94. juliaset = JuliaSet(task['width'],
  95. task['height'],
  96. task['xa'],
  97. task['xb'],
  98. task['ya'],
  99. task['yb'],
  100. task['iterations'])
  101. elapsed_time = time.time() - start_time
  102. LOG.info("task %s processed in %f seconds" %
  103. (task['uuid'], elapsed_time))
  104. filename = juliaset.get_file()
  105. LOG.debug("saved result of task %s to temporary file %s" %
  106. (task['uuid'], filename))
  107. with open(filename, "rb") as fp:
  108. size = os.fstat(fp.fileno()).st_size
  109. image = base64.b64encode(fp.read())
  110. checksum = hashlib.sha256(open(filename, 'rb').read()).hexdigest()
  111. os.remove(filename)
  112. LOG.debug("removed temporary file %s" % filename)
  113. result = {
  114. 'uuid': task['uuid'],
  115. 'duration': elapsed_time,
  116. 'image': image,
  117. 'checksum': checksum,
  118. 'size': size,
  119. 'generated_by': socket.gethostname()
  120. }
  121. # NOTE(berendt): only necessary when using requests < 2.4.2
  122. headers = {'Content-type': 'application/json',
  123. 'Accept': 'text/plain'}
  124. requests.put("%s/v1/fractal/%s" %
  125. (CONF.endpoint_url, str(task['uuid'])),
  126. json.dumps(result), headers=headers)
  127. message.ack()
  128. return result