stackamole-xblock 9.4.0__py3-none-any.whl

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.
Files changed (89) hide show
  1. fake/common/djangoapps/student/__init__.py +0 -0
  2. fake/common/djangoapps/student/models.py +15 -0
  3. fake/course/__init__.py +0 -0
  4. requirements/base.txt +25 -0
  5. requirements/flake8.txt +1 -0
  6. requirements/setup.txt +3 -0
  7. requirements/test.txt +19 -0
  8. samples/hot/sample-template.yaml +427 -0
  9. src/pip-delete-this-directory.txt +5 -0
  10. stackamole/__init__.py +8 -0
  11. stackamole/admin.py +130 -0
  12. stackamole/common.py +423 -0
  13. stackamole/jobs.py +211 -0
  14. stackamole/locale/config.yaml +14 -0
  15. stackamole/locale/de_DE/LC_MESSAGES/django.po +109 -0
  16. stackamole/locale/de_DE/LC_MESSAGES/djangojs.po +68 -0
  17. stackamole/locale/el/LC_MESSAGES/django.po +113 -0
  18. stackamole/locale/el/LC_MESSAGES/djangojs.po +77 -0
  19. stackamole/locale/en/LC_MESSAGES/django.po +107 -0
  20. stackamole/locale/en/LC_MESSAGES/djangojs.po +68 -0
  21. stackamole/locale/es_419/LC_MESSAGES/django.po +109 -0
  22. stackamole/locale/es_419/LC_MESSAGES/djangojs.po +68 -0
  23. stackamole/locale/et_EE/LC_MESSAGES/django.po +110 -0
  24. stackamole/locale/et_EE/LC_MESSAGES/djangojs.po +68 -0
  25. stackamole/locale/hi/LC_MESSAGES/django.po +111 -0
  26. stackamole/locale/hi/LC_MESSAGES/djangojs.po +72 -0
  27. stackamole/management/__init__.py +0 -0
  28. stackamole/management/commands/__init__.py +0 -0
  29. stackamole/management/commands/reaper.py +21 -0
  30. stackamole/management/commands/suspender.py +20 -0
  31. stackamole/migrations/0001_initial.py +43 -0
  32. stackamole/migrations/0002_stacklog.py +47 -0
  33. stackamole/migrations/0003_blanks.py +84 -0
  34. stackamole/migrations/0004_auto_20190715_1053.py +24 -0
  35. stackamole/migrations/0005_auto_20190811_1555.py +56 -0
  36. stackamole/migrations/0006_auto_20200107_1332.py +44 -0
  37. stackamole/migrations/0007_add_delete_by_and_delete_age.py +34 -0
  38. stackamole/migrations/0008_add_database_defaults_for_stack_key_and_password.py +23 -0
  39. stackamole/migrations/0009_add_null_true_for_key_and_password.py +23 -0
  40. stackamole/migrations/0010_add_user_foreign_key.py +52 -0
  41. stackamole/migrations/0011_allow_null_for_learner.py +29 -0
  42. stackamole/migrations/0012_add_suspend_by.py +21 -0
  43. stackamole/migrations/0013_migrate_app_label.py +71 -0
  44. stackamole/migrations/__init__.py +0 -0
  45. stackamole/models.py +106 -0
  46. stackamole/openstack.py +132 -0
  47. stackamole/provider.py +437 -0
  48. stackamole/public/css/main.css +203 -0
  49. stackamole/public/js/client.js +269 -0
  50. stackamole/public/js/guacamole-common-js/0.9.13-incubating-all.min.js +1 -0
  51. stackamole/public/js/guacamole-common-js/1.4.0-all.min.js +155 -0
  52. stackamole/public/js/guacamole-common-js/1.5.2-all.min.js +161 -0
  53. stackamole/public/js/guacamole-common-js/1.5.4-all.min.js +161 -0
  54. stackamole/public/js/guacamole-common-js/1.5.5-all.min.js +161 -0
  55. stackamole/public/js/main.js +492 -0
  56. stackamole/public/js/plugins.js +95 -0
  57. stackamole/stackamole.py +1226 -0
  58. stackamole/static/html/lab.html +88 -0
  59. stackamole/static/html/main.html +93 -0
  60. stackamole/tasks.py +947 -0
  61. stackamole_guacamole_client/__init__.py +0 -0
  62. stackamole_guacamole_client/asgi.py +26 -0
  63. stackamole_guacamole_client/consumers.py +95 -0
  64. stackamole_guacamole_client/settings.py +87 -0
  65. stackamole_xblock-9.4.0.dist-info/METADATA +647 -0
  66. stackamole_xblock-9.4.0.dist-info/RECORD +89 -0
  67. stackamole_xblock-9.4.0.dist-info/WHEEL +5 -0
  68. stackamole_xblock-9.4.0.dist-info/entry_points.txt +3 -0
  69. stackamole_xblock-9.4.0.dist-info/licenses/LICENSE +661 -0
  70. stackamole_xblock-9.4.0.dist-info/top_level.txt +7 -0
  71. tests/__init__.py +0 -0
  72. tests/resources/course/stackamole/fake_lab_1.xml +9 -0
  73. tests/resources/course/stackamole/fake_lab_2.xml +9 -0
  74. tests/resources/course/stackamole/fake_lab_hook_events_1.xml +4 -0
  75. tests/resources/course/stackamole/fake_lab_hook_events_2.xml +3 -0
  76. tests/resources/course/stackamole/fake_lab_no_port_name.xml +3 -0
  77. tests/resources/course/stackamole/fake_lab_no_port_number.xml +3 -0
  78. tests/resources/course/stackamole/fake_lab_no_provider_capacity.xml +5 -0
  79. tests/resources/course/stackamole/fake_lab_no_provider_name.xml +3 -0
  80. tests/unit/__init__.py +0 -0
  81. tests/unit/test_admin.py +170 -0
  82. tests/unit/test_commands.py +31 -0
  83. tests/unit/test_common.py +307 -0
  84. tests/unit/test_jobs.py +649 -0
  85. tests/unit/test_models.py +72 -0
  86. tests/unit/test_openstack.py +149 -0
  87. tests/unit/test_provider.py +847 -0
  88. tests/unit/test_stackamole.py +1664 -0
  89. tests/unit/test_tasks.py +2324 -0
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by Django 1.11.15 on 2019-07-15 10:53
3
+ from __future__ import unicode_literals
4
+
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('stackamole', '0003_blanks'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.RemoveField(
16
+ model_name='stacklog',
17
+ name='stack',
18
+ ),
19
+ migrations.AddField(
20
+ model_name='stacklog',
21
+ name='stack_id',
22
+ field=models.IntegerField(db_index=True, null=True),
23
+ ),
24
+ ]
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by Django 1.11.18 on 2019-08-11 15:55
3
+ from __future__ import unicode_literals
4
+
5
+ from django.db import migrations, models
6
+ import jsonfield.fields
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ('stackamole', '0004_auto_20190715_1053'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name='stack',
18
+ name='hook_events',
19
+ field=jsonfield.fields.JSONField(default="null"),
20
+ ),
21
+ migrations.AddField(
22
+ model_name='stack',
23
+ name='hook_script',
24
+ field=models.CharField(max_length=256, null=True),
25
+ ),
26
+ migrations.AddField(
27
+ model_name='stack',
28
+ name='providers',
29
+ field=jsonfield.fields.JSONField(default="null"),
30
+ ),
31
+ migrations.AddField(
32
+ model_name='stack',
33
+ name='run',
34
+ field=models.CharField(blank=True, max_length=50),
35
+ ),
36
+ migrations.AddField(
37
+ model_name='stacklog',
38
+ name='hook_events',
39
+ field=jsonfield.fields.JSONField(default="null"),
40
+ ),
41
+ migrations.AddField(
42
+ model_name='stacklog',
43
+ name='hook_script',
44
+ field=models.CharField(max_length=256, null=True),
45
+ ),
46
+ migrations.AddField(
47
+ model_name='stacklog',
48
+ name='providers',
49
+ field=jsonfield.fields.JSONField(default="null"),
50
+ ),
51
+ migrations.AddField(
52
+ model_name='stacklog',
53
+ name='run',
54
+ field=models.CharField(blank=True, max_length=50),
55
+ ),
56
+ ]
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by Django 1.11.21 on 2020-01-07 13:32
3
+ from __future__ import unicode_literals
4
+
5
+ from django.db import migrations
6
+ import jsonfield.fields
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ('stackamole', '0005_auto_20190811_1555'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AlterField(
17
+ model_name='stack',
18
+ name='hook_events',
19
+ field=jsonfield.fields.JSONField(default=dict),
20
+ ),
21
+ migrations.RunSQL(
22
+ "UPDATE stackamole_stack SET hook_events = '{}' WHERE hook_events = '\"null\"';", # noqa: E501
23
+ "UPDATE stackamole_stack SET hook_events = '\"null\"' WHERE hook_events = '{}';", # noqa: E501
24
+ ),
25
+ migrations.AlterField(
26
+ model_name='stack',
27
+ name='providers',
28
+ field=jsonfield.fields.JSONField(default=list),
29
+ ),
30
+ migrations.RunSQL(
31
+ "UPDATE stackamole_stack SET providers = '[]' WHERE providers = '\"null\"';", # noqa: E501
32
+ "UPDATE stackamole_stack SET providers = '\"null\"' WHERE providers = '[]';", # noqa: E501
33
+ ),
34
+ migrations.AlterField(
35
+ model_name='stacklog',
36
+ name='hook_events',
37
+ field=jsonfield.fields.JSONField(default=dict),
38
+ ),
39
+ migrations.AlterField(
40
+ model_name='stacklog',
41
+ name='providers',
42
+ field=jsonfield.fields.JSONField(default=list),
43
+ ),
44
+ ]
@@ -0,0 +1,34 @@
1
+ # Generated by Django 2.2.16 on 2020-11-04 13:35
2
+
3
+ from django.db import migrations, models
4
+ import stackamole.models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('stackamole', '0006_auto_20200107_1332'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='stack',
16
+ name='delete_age',
17
+ field=models.IntegerField(null=True),
18
+ ),
19
+ migrations.AddField(
20
+ model_name='stack',
21
+ name='delete_by',
22
+ field=models.DateTimeField(db_index=True, default=stackamole.models.default_delete_by_timestamp, null=True), # noqa E501
23
+ ),
24
+ migrations.AddField(
25
+ model_name='stacklog',
26
+ name='delete_age',
27
+ field=models.IntegerField(null=True),
28
+ ),
29
+ migrations.AddField(
30
+ model_name='stacklog',
31
+ name='delete_by',
32
+ field=models.DateTimeField(db_index=True, default=stackamole.models.default_delete_by_timestamp, null=True), # noqa E501
33
+ ),
34
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 2.2.19 on 2021-03-25 17:08
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('stackamole', '0007_add_delete_by_and_delete_age'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='stack',
15
+ name='key',
16
+ field=models.TextField(blank=True, default=''),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='stack',
20
+ name='password',
21
+ field=models.CharField(blank=True, default='', max_length=128),
22
+ ),
23
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 2.2.16 on 2021-03-30 11:28
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('stackamole', '0008_add_database_defaults_for_stack_key_and_password'), # noqa E501
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='stack',
15
+ name='key',
16
+ field=models.TextField(blank=True, null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='stack',
20
+ name='password',
21
+ field=models.CharField(blank=True, max_length=128, null=True),
22
+ ),
23
+ ]
@@ -0,0 +1,52 @@
1
+ from django.conf import settings
2
+ from django.core.exceptions import ObjectDoesNotExist
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+ import logging
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class Migration(migrations.Migration):
11
+
12
+ def backfill_learner(apps, schema_editor):
13
+ """
14
+ Use the 'student_id' to link stacks to the User model.
15
+ """
16
+ Stack = apps.get_model("stackamole", "Stack")
17
+ AnonymousUserId = apps.get_model("student", "AnonymousUserId")
18
+ for stack in Stack.objects.all():
19
+ try:
20
+ stack.learner = AnonymousUserId.objects.get(
21
+ anonymous_user_id=stack.student_id).user
22
+ stack.save(update_fields=['learner'])
23
+ except ObjectDoesNotExist:
24
+ logger.warning('Unable to link stack to user: '
25
+ f'{stack.name}')
26
+
27
+ dependencies = [
28
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
29
+ ('stackamole', '0009_add_null_true_for_key_and_password'),
30
+ ]
31
+
32
+ operations = [
33
+ migrations.AddField(
34
+ model_name='stack',
35
+ name='learner',
36
+ field=models.ForeignKey(
37
+ db_constraint=False,
38
+ null=True,
39
+ on_delete=django.db.models.deletion.PROTECT,
40
+ to=settings.AUTH_USER_MODEL),
41
+ ),
42
+ migrations.RunPython(backfill_learner),
43
+ migrations.AlterField(
44
+ model_name='stack',
45
+ name='learner',
46
+ field=models.ForeignKey(
47
+ db_constraint=True,
48
+ null=True,
49
+ on_delete=django.db.models.deletion.PROTECT,
50
+ to=settings.AUTH_USER_MODEL),
51
+ ),
52
+ ]
@@ -0,0 +1,29 @@
1
+ from django.conf import settings
2
+ from django.db import migrations, models
3
+ import django.db.models.deletion
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('stackamole', '0010_add_user_foreign_key'),
10
+ ]
11
+
12
+ # Because 0010_add_user_foreign_key was buggy in several releases,
13
+ # and those set the field to be NOT NULL, what we have to do here
14
+ # is undo that. For an installation that undergoes both
15
+ # 0010_add_user_foreign_key and this migration in a fixed release,
16
+ # this AlterField will be a no-op. For one that ran
17
+ # 0010_add_user_foreign_key from a broken release and thus had its
18
+ # field set to NOT NULL, this will allow NULL values.
19
+ operations = [
20
+ migrations.AlterField(
21
+ model_name='stack',
22
+ name='learner',
23
+ field=models.ForeignKey(
24
+ db_constraint=True,
25
+ null=True,
26
+ on_delete=django.db.models.deletion.PROTECT,
27
+ to=settings.AUTH_USER_MODEL),
28
+ ),
29
+ ]
@@ -0,0 +1,21 @@
1
+ from django.db import migrations, models
2
+
3
+
4
+ class Migration(migrations.Migration):
5
+
6
+ dependencies = [
7
+ ('stackamole', '0011_allow_null_for_learner'),
8
+ ]
9
+
10
+ operations = [
11
+ migrations.AddField(
12
+ model_name='stack',
13
+ name='suspend_by',
14
+ field=models.DateTimeField(db_index=True, null=True),
15
+ ),
16
+ migrations.AddField(
17
+ model_name='stacklog',
18
+ name='suspend_by',
19
+ field=models.DateTimeField(db_index=True, null=True),
20
+ ),
21
+ ]
@@ -0,0 +1,71 @@
1
+ from django.db import migrations
2
+
3
+ # Rename the 'hastexo' tables to 'stackamole' to access labs created
4
+ # prior to the project rename.
5
+
6
+ OLD_APP_LABEL = 'hastexo'
7
+ NEW_APP_LABEL = 'stackamole'
8
+
9
+
10
+ def migrate_tables(apps, schema_editor):
11
+ connection = schema_editor.connection
12
+ existing_tables = connection.introspection.table_names()
13
+
14
+ if 'hastexo_stack' in existing_tables:
15
+ with connection.cursor() as cursor:
16
+ # stackamole_* tables were created just now and contain no data,
17
+ # confirm empty and drop
18
+ cursor.execute("SELECT COUNT(*) FROM stackamole_stack")
19
+ if cursor.fetchone()[0] > 0:
20
+ raise Exception(
21
+ "'hastexo_stack' exists & 'stackamole_stack' contains "
22
+ "data; this scenario is out of scope for automated "
23
+ "migration.")
24
+ cursor.execute("DROP TABLE stackamole_stacklog")
25
+ cursor.execute("DROP TABLE stackamole_stack")
26
+ # rename hastexo_* tables to stackamole_*
27
+ cursor.execute(
28
+ "ALTER TABLE hastexo_stacklog RENAME TO stackamole_stacklog")
29
+ cursor.execute(
30
+ "ALTER TABLE hastexo_stack RENAME TO stackamole_stack")
31
+
32
+
33
+ def migrate_contenttypes(apps, schema_editor):
34
+ ContentType = apps.get_model('contenttypes', 'ContentType')
35
+ db = schema_editor.connection.alias
36
+ if ContentType.objects.using(db).filter(app_label=OLD_APP_LABEL).exists():
37
+ # remove the stackamole content types that were just auto-created
38
+ ContentType.objects.using(db).filter(app_label=NEW_APP_LABEL).delete()
39
+ # rename 'hastexo' content types to 'stackamole'
40
+ ContentType.objects.using(db).filter(app_label=OLD_APP_LABEL).update(
41
+ app_label=NEW_APP_LABEL)
42
+
43
+
44
+ def migrate_migration_records(apps, schema_editor):
45
+ # delete now orphaned 'hastexo' migration records
46
+ schema_editor.connection.cursor().execute(
47
+ "DELETE FROM django_migrations WHERE app = 'hastexo'"
48
+ )
49
+
50
+
51
+ class Migration(migrations.Migration):
52
+
53
+ dependencies = [
54
+ ('stackamole', '0012_add_suspend_by'),
55
+ ]
56
+
57
+ # One way migration only from 'hastexo' to 'stackamole'
58
+ operations = [
59
+ migrations.RunPython(
60
+ migrate_tables,
61
+ migrations.RunPython.noop,
62
+ ),
63
+ migrations.RunPython(
64
+ migrate_contenttypes,
65
+ migrations.RunPython.noop,
66
+ ),
67
+ migrations.RunPython(
68
+ migrate_migration_records,
69
+ migrations.RunPython.noop,
70
+ ),
71
+ ]
File without changes
stackamole/models.py ADDED
@@ -0,0 +1,106 @@
1
+ from django.conf import settings as django_settings
2
+ from django.db import models
3
+ from django.utils import timezone
4
+ from jsonfield.fields import JSONField
5
+
6
+ SETTINGS_KEY = 'stackamole'
7
+ DEFAULT_SETTINGS = {"delete_age": 14}
8
+
9
+
10
+ def default_delete_by_timestamp():
11
+ # load the default delete_age value from settings
12
+ try:
13
+ settings = django_settings.XBLOCK_SETTINGS.get(
14
+ SETTINGS_KEY,
15
+ DEFAULT_SETTINGS
16
+ )
17
+ except AttributeError:
18
+ settings = DEFAULT_SETTINGS
19
+
20
+ # create a delete_by timestamp based on the delete_age from settings
21
+ delete_by = timezone.now() + timezone.timedelta(
22
+ days=settings.get("delete_age", 14))
23
+
24
+ return delete_by
25
+
26
+
27
+ class StackCommon(models.Model):
28
+ """
29
+ Abstract class that defines a stack record's common fields.
30
+
31
+ """
32
+ class Meta:
33
+ abstract = True
34
+
35
+ name = models.CharField(max_length=64, db_index=True)
36
+ student_id = models.CharField(max_length=40, db_index=True)
37
+ course_id = models.CharField(max_length=50, db_index=True)
38
+ run = models.CharField(max_length=50, blank=True)
39
+ provider = models.CharField(max_length=32, blank=True)
40
+ providers = JSONField(default=list)
41
+ hook_script = models.CharField(max_length=256, null=True)
42
+ hook_events = JSONField(default=dict)
43
+ protocol = models.CharField(max_length=32, blank=True)
44
+ port = models.IntegerField(null=True)
45
+ status = models.CharField(max_length=32, blank=True, db_index=True)
46
+ error_msg = models.CharField(max_length=256, blank=True)
47
+ ip = models.GenericIPAddressField(null=True)
48
+ user = models.CharField(max_length=32, blank=True)
49
+ launch_task_id = models.CharField(max_length=40, blank=True)
50
+ launch_timestamp = models.DateTimeField(null=True, db_index=True)
51
+ suspend_timestamp = models.DateTimeField(null=True, db_index=True)
52
+ suspend_by = models.DateTimeField(null=True, db_index=True)
53
+ created_on = models.DateTimeField(auto_now_add=True, db_index=True)
54
+ delete_age = models.IntegerField(null=True)
55
+ delete_by = models.DateTimeField(null=True, db_index=True,
56
+ default=default_delete_by_timestamp)
57
+
58
+
59
+ class Stack(StackCommon):
60
+ """
61
+ Stores Heat stack data that needs to be queried across XBlock instances.
62
+
63
+ """
64
+ class Meta:
65
+ app_label = 'stackamole'
66
+ unique_together = (('student_id', 'course_id', 'name'),)
67
+
68
+ key = models.TextField(blank=True, null=True)
69
+ password = models.CharField(max_length=128, blank=True, null=True)
70
+ learner = models.ForeignKey(django_settings.AUTH_USER_MODEL,
71
+ null=True,
72
+ db_index=True,
73
+ on_delete=models.PROTECT)
74
+
75
+ def __init__(self, *args, **kwargs):
76
+ super(Stack, self).__init__(*args, **kwargs)
77
+
78
+ # Save previous status
79
+ self.prev_status = self.status
80
+
81
+ def save(self, *args, **kwargs):
82
+ super(Stack, self).save(*args, **kwargs)
83
+
84
+ # Populate the log if there was a status change
85
+ if self.status and self.status != self.prev_status:
86
+ self.prev_status = self.status
87
+
88
+ log_fields = {'stack_id': self.id}
89
+ for field in StackCommon._meta.get_fields():
90
+ if field.name == 'created_on':
91
+ continue
92
+ log_fields[field.name] = getattr(self, field.name)
93
+
94
+ log = StackLog(**log_fields)
95
+ log.save()
96
+
97
+
98
+ class StackLog(StackCommon):
99
+ """
100
+ A log of stack model changes.
101
+
102
+ """
103
+ class Meta:
104
+ app_label = 'stackamole'
105
+
106
+ stack_id = models.IntegerField(null=True, db_index=True)
@@ -0,0 +1,132 @@
1
+ from keystoneauth1.identity import generic
2
+ from keystoneauth1 import session as kssession
3
+ from heatclient import client as heat_client
4
+ from novaclient import client as nova_client
5
+
6
+
7
+ class OpenStackWrapper(object):
8
+ """
9
+ A meta-class for wrapping OpenStack services.
10
+
11
+ """
12
+ os_options = (
13
+ 'os_auth_url',
14
+ 'os_auth_token',
15
+ 'os_username',
16
+ 'os_password',
17
+ 'os_user_id',
18
+ 'os_user_domain_id',
19
+ 'os_user_domain_name',
20
+ 'os_tenant_id',
21
+ 'os_tenant_name',
22
+ 'os_project_id',
23
+ 'os_project_name',
24
+ 'os_project_domain_id',
25
+ 'os_project_domain_name',
26
+ 'os_region_name'
27
+ )
28
+
29
+ service_type = None
30
+ endpoint_type = None
31
+ api_version = None
32
+ options = {}
33
+
34
+ def __init__(self, **options):
35
+ # Set OpenStack options
36
+ for os_option in self.os_options:
37
+ self.options[os_option] = options.get(os_option)
38
+
39
+ def get_keystone_auth(self):
40
+ keystone_session = kssession.Session(verify=True)
41
+ if self.options['os_auth_token']:
42
+ kwargs = {
43
+ 'token': self.options['os_auth_token'],
44
+ 'auth_url': self.options['os_auth_url']
45
+ }
46
+ keystone_auth = generic.Token(**kwargs)
47
+ else:
48
+ project_id = self.options['os_project_id'] or self.options['os_tenant_id'] # noqa: E501
49
+ project_name = self.options['os_project_name'] or self.options['os_tenant_name'] # noqa: E501
50
+ kwargs = {
51
+ 'username': self.options['os_username'],
52
+ 'user_id': self.options['os_user_id'],
53
+ 'user_domain_id': self.options['os_user_domain_id'],
54
+ 'user_domain_name': self.options['os_user_domain_name'],
55
+ 'password': self.options['os_password'],
56
+ 'auth_url': self.options['os_auth_url'],
57
+ 'project_id': project_id,
58
+ 'project_name': project_name,
59
+ 'project_domain_id': self.options['os_project_domain_id'],
60
+ 'project_domain_name': self.options['os_project_domain_name'],
61
+ }
62
+ keystone_auth = generic.Password(**kwargs)
63
+
64
+ return (keystone_session, keystone_auth)
65
+
66
+
67
+ class HeatWrapper(OpenStackWrapper):
68
+ """
69
+ A class that wraps the Heat service for the Stackamole XBlock.
70
+
71
+ """
72
+ def __init__(self, **options):
73
+ self.service_type = 'orchestration'
74
+ self.endpoint_type = 'publicURL'
75
+ self.api_version = '1'
76
+
77
+ super(HeatWrapper, self).__init__(**options)
78
+
79
+ def get_client(self):
80
+ """
81
+ This function is based, with minor changes, on the official OpenStack
82
+ Heat client's shell implementation. Specifically,
83
+ python-heatclient/heatclient/shell.py.
84
+
85
+ """
86
+ keystone_session, keystone_auth = self.get_keystone_auth()
87
+
88
+ kwargs = {
89
+ 'auth_url': self.options['os_auth_url'],
90
+ 'session': keystone_session,
91
+ 'auth': keystone_auth,
92
+ 'service_type': self.service_type,
93
+ 'endpoint_type': self.endpoint_type,
94
+ 'region_name': self.options['os_region_name'],
95
+ 'username': self.options['os_username'],
96
+ 'password': self.options['os_password']
97
+ }
98
+
99
+ return heat_client.Client(self.api_version, **kwargs)
100
+
101
+
102
+ class NovaWrapper(OpenStackWrapper):
103
+ """
104
+ A class that wraps the Compute service for the Stackamole XBlock.
105
+
106
+ """
107
+ def __init__(self, **options):
108
+ self.service_type = 'compute'
109
+ self.endpoint_type = 'publicURL'
110
+ self.api_version = '2.2'
111
+
112
+ super(NovaWrapper, self).__init__(**options)
113
+
114
+ def get_client(self):
115
+ keystone_session, keystone_auth = self.get_keystone_auth()
116
+ return nova_client.Client(
117
+ self.api_version,
118
+ self.options['os_username'],
119
+ self.options['os_password'],
120
+ project_id=self.options['os_project_id'],
121
+ project_name=self.options['os_project_name'],
122
+ user_id=self.options['os_user_id'],
123
+ auth_url=self.options['os_auth_url'],
124
+ region_name=self.options['os_region_name'],
125
+ endpoint_type=self.endpoint_type,
126
+ service_type=self.service_type,
127
+ session=keystone_session,
128
+ auth=keystone_auth,
129
+ project_domain_id=self.options['os_project_domain_id'],
130
+ project_domain_name=self.options['os_project_domain_name'],
131
+ user_domain_id=self.options['os_user_domain_id'],
132
+ user_domain_name=self.options['os_user_domain_name'])