From d4a5bc97f394985e2a53dca924974514c6fda8c3 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 2 Mar 2011 17:20:17 -0800 Subject: [PATCH] Added django-nova to openstack-dashboard repo --- .bzrignore | 7 +- README | 49 -- django-nova/LICENSE | 176 +++++++ django-nova/README | 42 ++ django-nova/bootstrap.py | 260 +++++++++ django-nova/buildout.cfg | 19 + django-nova/setup.py | 29 + .../src/django_nova}/__init__.py | 0 django-nova/src/django_nova/adminclient.py | 498 ++++++++++++++++++ django-nova/src/django_nova/connection.py | 38 ++ django-nova/src/django_nova/exceptions.py | 95 ++++ django-nova/src/django_nova/forms.py | 262 +++++++++ .../src/django_nova/management}/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/createnovausers.py | 37 ++ django-nova/src/django_nova/manager.py | 340 ++++++++++++ django-nova/src/django_nova/models.py | 121 +++++ django-nova/src/django_nova/shortcuts.py | 131 +++++ .../django_nova/project/add_project.html | 45 ++ .../django_nova/project/add_project_user.html | 69 +++ .../django_nova/project/base_projects.html | 16 + .../django_nova/project/change_list.html | 3 + .../django_nova/project/delete_project.html | 25 + .../project/delete_project_user.html | 25 + .../django_nova/project/edit_project.html | 95 ++++ .../django_nova/project/global_edit_user.html | 71 +++ .../django_nova/project/project_list.html | 42 ++ .../django_nova/project/project_user.html | 76 +++ .../django_nova/project/send_credentials.html | 87 +++ .../admin/django_nova/project/user_list.html | 39 ++ .../templates/django_nova/_messages.html | 41 ++ .../templates/django_nova/base.html | 85 +++ .../django_nova/credentials/expired.html | 17 + .../django_nova/images/_launch_form.html | 7 + .../templates/django_nova/images/_list.html | 112 ++++ .../templates/django_nova/images/base.html | 7 + .../django_nova/images/detail_list.html | 207 ++++++++ .../templates/django_nova/images/edit.html | 35 ++ .../templates/django_nova/images/index.html | 70 +++ .../templates/django_nova/images/launch.html | 32 ++ .../instances/_instances_list.html | 104 ++++ .../templates/django_nova/instances/base.html | 7 + .../django_nova/instances/detail_list.html | 33 ++ .../templates/django_nova/instances/edit.html | 34 ++ .../django_nova/instances/index.html | 98 ++++ .../django_nova/instances/performance.html | 58 ++ .../django_nova/keypairs/_create_form.html | 5 + .../templates/django_nova/keypairs/_list.html | 31 ++ .../templates/django_nova/keypairs/base.html | 7 + .../templates/django_nova/keypairs/index.html | 77 +++ .../django_nova/projects/edit_user.html | 72 +++ .../templates/django_nova/projects/index.html | 26 + .../django_nova/projects/manage.html | 45 ++ .../securitygroups/_authorize_form.html | 5 + .../securitygroups/_create_form.html | 5 + .../securitygroups/_revoke_form.html | 3 + .../django_nova/securitygroups/base.html | 7 + .../django_nova/securitygroups/detail.html | 62 +++ .../django_nova/securitygroups/index.html | 59 +++ .../django_nova/volumes/_attach_form.html | 5 + .../django_nova/volumes/_create_form.html | 5 + .../templates/django_nova/volumes/base.html | 7 + .../templates/django_nova/volumes/index.html | 84 +++ .../src/django_nova/templatetags/__init__.py | 0 .../django_nova/templatetags/admin_extras.py | 50 ++ .../templatetags/django_nova_tags.py | 37 ++ .../django_nova/templatetags/project_tags.py | 39 ++ .../django_nova/templatetags/region_tags.py | 40 ++ .../django_nova/templatetags/sidebar_tags.py | 46 ++ .../templatetags/truncate_filter.py | 31 ++ django-nova/src/django_nova/tests/__init__.py | 1 + .../tests/templates/base-sidebar.html | 0 django-nova/src/django_nova/tests/urls.py | 36 ++ .../django_nova/tests/view_tests/__init__.py | 7 + .../src/django_nova/tests/view_tests/base.py | 90 ++++ .../tests/view_tests/credential_tests.py | 70 +++ .../tests/view_tests/image_tests.py | 232 ++++++++ .../tests/view_tests/instance_tests.py | 67 +++ .../tests/view_tests/keypair_tests.py | 93 ++++ .../tests/view_tests/region_tests.py | 43 ++ .../tests/view_tests/volume_tests.py | 170 ++++++ django-nova/src/django_nova/testsettings.py | 21 + django-nova/src/django_nova/urls/__init__.py | 0 .../src/django_nova/urls/admin_project.py | 55 ++ .../src/django_nova/urls/admin_roles.py | 32 ++ django-nova/src/django_nova/urls/project.py | 129 +++++ django-nova/src/django_nova/urls/region.py | 29 + django-nova/src/django_nova/views/__init__.py | 0 django-nova/src/django_nova/views/admin.py | 326 ++++++++++++ .../src/django_nova/views/credentials.py | 46 ++ django-nova/src/django_nova/views/images.py | 229 ++++++++ .../src/django_nova/views/instances.py | 203 +++++++ django-nova/src/django_nova/views/keypairs.py | 122 +++++ django-nova/src/django_nova/views/projects.py | 107 ++++ django-nova/src/django_nova/views/regions.py | 36 ++ .../src/django_nova/views/securitygroups.py | 180 +++++++ django-nova/src/django_nova/views/volumes.py | 151 ++++++ openstack-dashboard/README | 49 ++ openstack-dashboard/dashboard/__init__.py | 0 .../dashboard}/manage.py | 0 .../dashboard}/settings.py | 0 .../dashboard}/templates/403.html | 0 .../dashboard}/templates/404.html | 0 .../dashboard}/templates/500.html | 0 .../dashboard}/templates/admin/index.html | 0 .../dashboard}/templates/base-root.html | 0 .../dashboard}/templates/base-sidebar.html | 0 .../dashboard}/templates/base.html | 0 .../dashboard}/templates/index.html | 4 +- .../templates/permission_denied.html | 0 .../templates/registration/activate.html | 0 .../registration/activation_email.txt | 0 .../registration/activation_email_subject.txt | 0 .../templates/registration/login.html | 0 .../templates/registration/logout.html | 0 .../registration/password_change_done.html | 0 .../registration/password_change_form.html | 0 .../registration/password_reset_complete.html | 0 .../registration/password_reset_confirm.html | 0 .../registration/password_reset_done.html | 0 .../registration/password_reset_email.html | 0 .../registration/password_reset_form.html | 0 .../registration/registration_complete.html | 0 .../registration/registration_form.html | 0 .../dashboard}/templates/unavailable.html | 0 .../dashboard}/urls.py | 0 .../dashboard}/views.py | 0 .../dashboard}/wsgi/django.wsgi | 0 openstack-dashboard/local/__init__.py | 0 .../local}/local_settings.py.example | 0 .../ui-bg_diagonals-thick_90_eeeeee_40x40.png | Bin .../images/ui-bg_flat_15_cd0a0a_40x100.png | Bin .../images/ui-bg_glass_100_e4f1fb_1x400.png | Bin .../images/ui-bg_glass_50_3baae3_1x400.png | Bin .../images/ui-bg_glass_80_d7ebf9_1x400.png | Bin .../ui-bg_highlight-hard_100_f2f5f7_1x100.png | Bin .../ui-bg_highlight-hard_70_000000_1x100.png | Bin .../ui-bg_highlight-soft_100_deedf7_1x100.png | Bin .../ui-bg_highlight-soft_25_ffef8f_1x100.png | Bin .../images/ui-icons_2694e8_256x240.png | Bin .../images/ui-icons_2e83ff_256x240.png | Bin .../images/ui-icons_3d80b3_256x240.png | Bin .../images/ui-icons_72a7cf_256x240.png | Bin .../images/ui-icons_ffffff_256x240.png | Bin .../css/cupertino/jquery-ui-1.7.2.custom.css | 0 .../dashboard/css/django-admin-widgets.css | 0 .../media}/dashboard/css/ie7.css | 0 .../media}/dashboard/css/openstack.css | 0 .../media}/dashboard/css/reset.css | 0 .../media}/dashboard/img/body_bg.gif | Bin .../media}/dashboard/img/body_bg.png | Bin .../media}/dashboard/img/book_icon.png | Bin .../media}/dashboard/img/bread_crumb.gif | Bin .../media}/dashboard/img/btn_bg.png | Bin .../media}/dashboard/img/chat_icon.png | Bin .../media}/dashboard/img/content_bg.gif | Bin .../media}/dashboard/img/content_shadow.png | Bin .../media}/dashboard/img/dashboard_nav_bg.png | Bin .../media}/dashboard/img/foot_back.png | Bin .../media}/dashboard/img/gears.png | Bin .../media}/dashboard/img/header_bg.png | Bin .../media}/dashboard/img/home_head_back.png | Bin .../media}/dashboard/img/image_detail.png | Bin .../media}/dashboard/img/logged_in_box_bg.gif | Bin .../media}/dashboard/img/login_bg.png | Bin .../media}/dashboard/img/login_btn.png | Bin .../media}/dashboard/img/logo.gif | Bin .../media}/dashboard/img/nav_arrow.png | Bin .../media}/dashboard/img/nav_bg.png | Bin .../media}/dashboard/img/nav_highlight.png | Bin .../media}/dashboard/img/page_header.png | Bin .../media}/dashboard/img/projects_bg.png | Bin .../media}/dashboard/img/server_icon.png | Bin .../media}/dashboard/img/spinner.gif | Bin .../media}/dashboard/img/sub-head-back.png | Bin .../media}/dashboard/img/sub_head_back.png | Bin .../media}/dashboard/img/table_header_bg.png | Bin .../media}/dashboard/img/table_heading_bg.png | Bin .../dashboard/img/title-blank-short-foot.png | Bin .../media}/dashboard/js/dashboard.js | 0 .../dashboard/js/django-admin.multiselect.js | 0 .../media}/dashboard/js/jquery-ui.min.js | 0 .../media}/dashboard/js/jquery.form.js | 0 .../media}/dashboard/js/jquery.min.js | 0 .../run_tests.sh | 0 .../tools}/install_venv.py | 24 +- .../tools}/pip-requires | 0 .../tools}/with_venv.sh | 0 188 files changed, 6872 insertions(+), 72 deletions(-) create mode 100644 django-nova/LICENSE create mode 100644 django-nova/README create mode 100644 django-nova/bootstrap.py create mode 100644 django-nova/buildout.cfg create mode 100644 django-nova/setup.py rename {dashboard => django-nova/src/django_nova}/__init__.py (100%) create mode 100644 django-nova/src/django_nova/adminclient.py create mode 100644 django-nova/src/django_nova/connection.py create mode 100644 django-nova/src/django_nova/exceptions.py create mode 100644 django-nova/src/django_nova/forms.py rename {local => django-nova/src/django_nova/management}/__init__.py (100%) create mode 100644 django-nova/src/django_nova/management/commands/__init__.py create mode 100644 django-nova/src/django_nova/management/commands/createnovausers.py create mode 100644 django-nova/src/django_nova/manager.py create mode 100644 django-nova/src/django_nova/models.py create mode 100644 django-nova/src/django_nova/shortcuts.py create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html create mode 100644 django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html create mode 100644 django-nova/src/django_nova/templates/django_nova/_messages.html create mode 100644 django-nova/src/django_nova/templates/django_nova/base.html create mode 100644 django-nova/src/django_nova/templates/django_nova/credentials/expired.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/_launch_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/_list.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/base.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/detail_list.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/edit.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/index.html create mode 100644 django-nova/src/django_nova/templates/django_nova/images/launch.html create mode 100644 django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html create mode 100644 django-nova/src/django_nova/templates/django_nova/instances/base.html create mode 100644 django-nova/src/django_nova/templates/django_nova/instances/detail_list.html create mode 100644 django-nova/src/django_nova/templates/django_nova/instances/edit.html create mode 100644 django-nova/src/django_nova/templates/django_nova/instances/index.html create mode 100644 django-nova/src/django_nova/templates/django_nova/instances/performance.html create mode 100644 django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/keypairs/_list.html create mode 100644 django-nova/src/django_nova/templates/django_nova/keypairs/base.html create mode 100644 django-nova/src/django_nova/templates/django_nova/keypairs/index.html create mode 100644 django-nova/src/django_nova/templates/django_nova/projects/edit_user.html create mode 100644 django-nova/src/django_nova/templates/django_nova/projects/index.html create mode 100644 django-nova/src/django_nova/templates/django_nova/projects/manage.html create mode 100644 django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/securitygroups/base.html create mode 100644 django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html create mode 100644 django-nova/src/django_nova/templates/django_nova/securitygroups/index.html create mode 100644 django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html create mode 100644 django-nova/src/django_nova/templates/django_nova/volumes/base.html create mode 100644 django-nova/src/django_nova/templates/django_nova/volumes/index.html create mode 100644 django-nova/src/django_nova/templatetags/__init__.py create mode 100644 django-nova/src/django_nova/templatetags/admin_extras.py create mode 100644 django-nova/src/django_nova/templatetags/django_nova_tags.py create mode 100644 django-nova/src/django_nova/templatetags/project_tags.py create mode 100644 django-nova/src/django_nova/templatetags/region_tags.py create mode 100644 django-nova/src/django_nova/templatetags/sidebar_tags.py create mode 100644 django-nova/src/django_nova/templatetags/truncate_filter.py create mode 100644 django-nova/src/django_nova/tests/__init__.py create mode 100644 django-nova/src/django_nova/tests/templates/base-sidebar.html create mode 100644 django-nova/src/django_nova/tests/urls.py create mode 100644 django-nova/src/django_nova/tests/view_tests/__init__.py create mode 100644 django-nova/src/django_nova/tests/view_tests/base.py create mode 100644 django-nova/src/django_nova/tests/view_tests/credential_tests.py create mode 100644 django-nova/src/django_nova/tests/view_tests/image_tests.py create mode 100644 django-nova/src/django_nova/tests/view_tests/instance_tests.py create mode 100644 django-nova/src/django_nova/tests/view_tests/keypair_tests.py create mode 100644 django-nova/src/django_nova/tests/view_tests/region_tests.py create mode 100644 django-nova/src/django_nova/tests/view_tests/volume_tests.py create mode 100644 django-nova/src/django_nova/testsettings.py create mode 100644 django-nova/src/django_nova/urls/__init__.py create mode 100644 django-nova/src/django_nova/urls/admin_project.py create mode 100644 django-nova/src/django_nova/urls/admin_roles.py create mode 100644 django-nova/src/django_nova/urls/project.py create mode 100644 django-nova/src/django_nova/urls/region.py create mode 100644 django-nova/src/django_nova/views/__init__.py create mode 100644 django-nova/src/django_nova/views/admin.py create mode 100644 django-nova/src/django_nova/views/credentials.py create mode 100644 django-nova/src/django_nova/views/images.py create mode 100644 django-nova/src/django_nova/views/instances.py create mode 100644 django-nova/src/django_nova/views/keypairs.py create mode 100644 django-nova/src/django_nova/views/projects.py create mode 100644 django-nova/src/django_nova/views/regions.py create mode 100644 django-nova/src/django_nova/views/securitygroups.py create mode 100644 django-nova/src/django_nova/views/volumes.py create mode 100644 openstack-dashboard/README create mode 100644 openstack-dashboard/dashboard/__init__.py rename {dashboard => openstack-dashboard/dashboard}/manage.py (100%) rename {dashboard => openstack-dashboard/dashboard}/settings.py (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/403.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/404.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/500.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/admin/index.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/base-root.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/base-sidebar.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/base.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/index.html (92%) rename {dashboard => openstack-dashboard/dashboard}/templates/permission_denied.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/activate.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/activation_email.txt (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/activation_email_subject.txt (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/login.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/logout.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_change_done.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_change_form.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_reset_complete.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_reset_confirm.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_reset_done.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_reset_email.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/password_reset_form.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/registration_complete.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/registration/registration_form.html (100%) rename {dashboard => openstack-dashboard/dashboard}/templates/unavailable.html (100%) rename {dashboard => openstack-dashboard/dashboard}/urls.py (100%) rename {dashboard => openstack-dashboard/dashboard}/views.py (100%) rename {dashboard => openstack-dashboard/dashboard}/wsgi/django.wsgi (100%) create mode 100644 openstack-dashboard/local/__init__.py rename {local => openstack-dashboard/local}/local_settings.py.example (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png (100%) rename {media => openstack-dashboard/media}/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css (100%) rename {media => openstack-dashboard/media}/dashboard/css/django-admin-widgets.css (100%) rename {media => openstack-dashboard/media}/dashboard/css/ie7.css (100%) rename {media => openstack-dashboard/media}/dashboard/css/openstack.css (100%) rename {media => openstack-dashboard/media}/dashboard/css/reset.css (100%) rename {media => openstack-dashboard/media}/dashboard/img/body_bg.gif (100%) rename {media => openstack-dashboard/media}/dashboard/img/body_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/book_icon.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/bread_crumb.gif (100%) rename {media => openstack-dashboard/media}/dashboard/img/btn_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/chat_icon.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/content_bg.gif (100%) rename {media => openstack-dashboard/media}/dashboard/img/content_shadow.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/dashboard_nav_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/foot_back.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/gears.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/header_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/home_head_back.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/image_detail.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/logged_in_box_bg.gif (100%) rename {media => openstack-dashboard/media}/dashboard/img/login_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/login_btn.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/logo.gif (100%) rename {media => openstack-dashboard/media}/dashboard/img/nav_arrow.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/nav_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/nav_highlight.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/page_header.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/projects_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/server_icon.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/spinner.gif (100%) rename {media => openstack-dashboard/media}/dashboard/img/sub-head-back.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/sub_head_back.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/table_header_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/table_heading_bg.png (100%) rename {media => openstack-dashboard/media}/dashboard/img/title-blank-short-foot.png (100%) rename {media => openstack-dashboard/media}/dashboard/js/dashboard.js (100%) rename {media => openstack-dashboard/media}/dashboard/js/django-admin.multiselect.js (100%) rename {media => openstack-dashboard/media}/dashboard/js/jquery-ui.min.js (100%) rename {media => openstack-dashboard/media}/dashboard/js/jquery.form.js (100%) rename {media => openstack-dashboard/media}/dashboard/js/jquery.min.js (100%) rename run_tests.sh => openstack-dashboard/run_tests.sh (100%) rename {tools => openstack-dashboard/tools}/install_venv.py (87%) rename {tools => openstack-dashboard/tools}/pip-requires (100%) rename {tools => openstack-dashboard/tools}/with_venv.sh (100%) diff --git a/.bzrignore b/.bzrignore index ea457b591..03f01515d 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,3 +1,4 @@ -.dashboard-venv -local/dashboard_openstack.sqlite3 -local/local_settings.py +django-nova/src/django_nova.egg-info +openstack-dashboard/.dashboard-venv +openstack-dashboard/local/dashboard_openstack.sqlite3 +openstack-dashboard/local/local_settings.py diff --git a/README b/README index f4b1cfcbf..e69de29bb 100644 --- a/README +++ b/README @@ -1,49 +0,0 @@ -OpenStack Dashboard -------------------- - -The OpenStack Dashboard is a reference implementation of a Django site that -uses the Django-Nova project to provide web based interactions with the -OpenStack Nova cloud controller. - -For more information about the Django-Nova project, please visit: - - http://launchpad.net/django-nova - - -Getting Started ---------------- - -The first step is to obtain a local copy of the django-nova project: - - $ mkdir django-nova - $ cd django-nova - $ bzr init-repo . - $ bzr branch lp:django-nova/trunk - - -Next we will create the virtualenv for local development. A tool is included to -create one for you: - - $ python tools/install_venv.py - - -Now that the virtualenv is created, you need to configure your local -environment. To do this, create a local_settings.py file in the local/ -directory. There is a local_settings.py.example file there that may be used -as a template. - -Finally, issue the django syncdb command: - - $ tools/with_venv.sh dashboard/manage.py syncdb - -If after you have specified the admin user the script appears to hang, it -probably means the installation of Nova being referred to in local_settings.py -is unavailable. - - -If all is well you should now able to run the server locally: - - $ tools/with_venv.sh dashboard/manage.py runserver - - - diff --git a/django-nova/LICENSE b/django-nova/LICENSE new file mode 100644 index 000000000..68c771a09 --- /dev/null +++ b/django-nova/LICENSE @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/django-nova/README b/django-nova/README new file mode 100644 index 000000000..cc89b6cfb --- /dev/null +++ b/django-nova/README @@ -0,0 +1,42 @@ +OpenStack Django-Nova +--------------------- + +The Django-Nova project is a Django module that is used to provide web based +interactions with the OpenStack Nova cloud controller. + +There is a reference implementation that uses this module located at: + + http://launchpad.net/openstack-dashboard + +It is highly recommended that you make use of this reference implementation +so that changes you make can be visualized effectively and are consistent. +Using this reference implementation as a development environment will greatly +simplify development of the django-nova module. + +Of course, if you are developing your own Django site using django-nova, then +you can disregard this advice. + + + +Getting Started +--------------- + +Django-Nova uses Buildout (http://www.buildout.org/) to manage local +development. To configure your local Buildout environment: + + $ python bootstrap.py + $ bin/buildout + +This will install all the dependencies of django-nova and provide some useful +scripts in the bin/ directory: + + bin/python provides a python shell for the current buildout. + bin/django provides django functions for the current buildout. + + +You should now be able to run unit tests as follows: + + $ bin/django test + + + diff --git a/django-nova/bootstrap.py b/django-nova/bootstrap.py new file mode 100644 index 000000000..5f2cb0835 --- /dev/null +++ b/django-nova/bootstrap.py @@ -0,0 +1,260 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess +from optparse import OptionParser + +if sys.platform == 'win32': + def quote(c): + if ' ' in c: + return '"%s"' % c # work around spawn lamosity on windows + else: + return c +else: + quote = str + +# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. +stdout, stderr = subprocess.Popen( + [sys.executable, '-Sc', + 'try:\n' + ' import ConfigParser\n' + 'except ImportError:\n' + ' print 1\n' + 'else:\n' + ' print 0\n'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() +has_broken_dash_S = bool(int(stdout.strip())) + +# In order to be more robust in the face of system Pythons, we want to +# run without site-packages loaded. This is somewhat tricky, in +# particular because Python 2.6's distutils imports site, so starting +# with the -S flag is not sufficient. However, we'll start with that: +if not has_broken_dash_S and 'site' in sys.modules: + # We will restart with python -S. + args = sys.argv[:] + args[0:0] = [sys.executable, '-S'] + args = map(quote, args) + os.execv(sys.executable, args) +# Now we are running with -S. We'll get the clean sys.path, import site +# because distutils will do it later, and then reset the path and clean +# out any namespace packages from site-packages that might have been +# loaded by .pth files. +clean_path = sys.path[:] +import site +sys.path[:] = clean_path +for k, v in sys.modules.items(): + if k in ('setuptools', 'pkg_resources') or ( + hasattr(v, '__path__') and + len(v.__path__)==1 and + not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): + # This is a namespace package. Remove it. + sys.modules.pop(k) + +is_jython = sys.platform.startswith('java') + +setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' +distribute_source = 'http://python-distribute.org/distribute_setup.py' + +# parsing arguments +def normalize_to_url(option, opt_str, value, parser): + if value: + if '://' not in value: # It doesn't smell like a URL. + value = 'file://%s' % ( + urllib.pathname2url( + os.path.abspath(os.path.expanduser(value))),) + if opt_str == '--download-base' and not value.endswith('/'): + # Download base needs a trailing slash to make the world happy. + value += '/' + else: + value = None + name = opt_str[2:].replace('-', '_') + setattr(parser.values, name, value) + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --setup-source and --download-base to point to +local resources, you can keep this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("-v", "--version", dest="version", + help="use a specific zc.buildout version") +parser.add_option("-d", "--distribute", + action="store_true", dest="use_distribute", default=False, + help="Use Distribute rather than Setuptools.") +parser.add_option("--setup-source", action="callback", dest="setup_source", + callback=normalize_to_url, nargs=1, type="string", + help=("Specify a URL or file location for the setup file. " + "If you use Setuptools, this will default to " + + setuptools_source + "; if you use Distribute, this " + "will default to " + distribute_source +".")) +parser.add_option("--download-base", action="callback", dest="download_base", + callback=normalize_to_url, nargs=1, type="string", + help=("Specify a URL or directory for downloading " + "zc.buildout and either Setuptools or Distribute. " + "Defaults to PyPI.")) +parser.add_option("--eggs", + help=("Specify a directory for storing eggs. Defaults to " + "a temporary directory that is deleted when the " + "bootstrap script completes.")) +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --version, the " + "bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", None, action="store", dest="config_file", + help=("Specify the path to the buildout configuration " + "file to be used.")) + +options, args = parser.parse_args() + +# if -c was provided, we push it back into args for buildout's main function +if options.config_file is not None: + args += ['-c', options.config_file] + +if options.eggs: + eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) +else: + eggs_dir = tempfile.mkdtemp() + +if options.setup_source is None: + if options.use_distribute: + options.setup_source = distribute_source + else: + options.setup_source = setuptools_source + +if options.accept_buildout_test_releases: + args.append('buildout:accept-buildout-test-releases=true') +args.append('bootstrap') + +try: + import pkg_resources + import setuptools # A flag. Sometimes pkg_resources is installed alone. + if not hasattr(pkg_resources, '_distribute'): + raise ImportError +except ImportError: + ez_code = urllib2.urlopen( + options.setup_source).read().replace('\r\n', '\n') + ez = {} + exec ez_code in ez + setup_args = dict(to_dir=eggs_dir, download_delay=0) + if options.download_base: + setup_args['download_base'] = options.download_base + if options.use_distribute: + setup_args['no_fake'] = True + ez['use_setuptools'](**setup_args) + if 'pkg_resources' in sys.modules: + reload(sys.modules['pkg_resources']) + import pkg_resources + # This does not (always?) update the default working set. We will + # do it. + for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +cmd = [quote(sys.executable), + '-c', + quote('from setuptools.command.easy_install import main; main()'), + '-mqNxd', + quote(eggs_dir)] + +if not has_broken_dash_S: + cmd.insert(1, '-S') + +find_links = options.download_base +if not find_links: + find_links = os.environ.get('bootstrap-testing-find-links') +if find_links: + cmd.extend(['-f', quote(find_links)]) + +if options.use_distribute: + setup_requirement = 'distribute' +else: + setup_requirement = 'setuptools' +ws = pkg_resources.working_set +setup_requirement_path = ws.find( + pkg_resources.Requirement.parse(setup_requirement)).location +env = dict( + os.environ, + PYTHONPATH=setup_requirement_path) + +requirement = 'zc.buildout' +version = options.version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( + search_path=[setup_requirement_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +if is_jython: + import subprocess + exitcode = subprocess.Popen(cmd, env=env).wait() +else: # Windows prefers this, apparently; otherwise we would prefer subprocess + exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) +if exitcode != 0: + sys.stdout.flush() + sys.stderr.flush() + print ("An error occurred when trying to install zc.buildout. " + "Look above this message for any errors that " + "were output by easy_install.") + sys.exit(exitcode) + +ws.add_entry(eggs_dir) +ws.require(requirement) +import zc.buildout.buildout +zc.buildout.buildout.main(args) +if not options.eggs: # clean up temporary egg directory + shutil.rmtree(eggs_dir) diff --git a/django-nova/buildout.cfg b/django-nova/buildout.cfg new file mode 100644 index 000000000..c41ec9ae7 --- /dev/null +++ b/django-nova/buildout.cfg @@ -0,0 +1,19 @@ +[buildout] +parts = python django +develop = . +eggs = django-nova + +[python] +recipe = zc.recipe.egg +interpreter = python +eggs = ${buildout:eggs} + +[django] +recipe = djangorecipe +version = 1.2.4 +project = django_nova +projectegg = django_nova +settings = testsettings +test = django_nova +eggs = ${buildout:eggs} + diff --git a/django-nova/setup.py b/django-nova/setup.py new file mode 100644 index 000000000..838e652fd --- /dev/null +++ b/django-nova/setup.py @@ -0,0 +1,29 @@ +import os +from setuptools import setup, find_packages + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +setup( + name = "django-nova", + version = "0.1", + url = 'https://launchpad.net/django-nova/', + license = 'Apache 2.0', + description = "A Django interface for OpenStack Nova.", + long_description = read('README'), + author = 'Devin Carlen', + author_email = 'devin.carlen@gmail.com', + packages = find_packages('src'), + package_dir = {'': 'src'}, + install_requires = ['setuptools', 'boto==1.9b', 'mox>=0.5.0'], + classifiers = [ + 'Development Status :: 4 - Beta', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP', + ] +) + diff --git a/dashboard/__init__.py b/django-nova/src/django_nova/__init__.py similarity index 100% rename from dashboard/__init__.py rename to django-nova/src/django_nova/__init__.py diff --git a/django-nova/src/django_nova/adminclient.py b/django-nova/src/django_nova/adminclient.py new file mode 100644 index 000000000..a6af95ca4 --- /dev/null +++ b/django-nova/src/django_nova/adminclient.py @@ -0,0 +1,498 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Nova User API client library. +""" + +import base64 +import boto +import boto.exception +import httplib +import re +import string +from boto.ec2.regioninfo import RegionInfo + + +DEFAULT_CLC_URL='http://127.0.0.1:8773' +DEFAULT_REGION='nova' +DEFAULT_ACCESS_KEY='admin' +DEFAULT_SECRET_KEY='admin' + + +class UserInfo(object): + """ + Information about a Nova user, as parsed through SAX + fields include: + username + accesskey + secretkey + + and an optional field containing a zip with X509 cert & rc + file + """ + + def __init__(self, connection=None, username=None, endpoint=None): + self.connection = connection + self.username = username + self.endpoint = endpoint + + def __repr__(self): + return 'UserInfo:%s' % self.username + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'username': + self.username = str(value) + elif name == 'file': + self.file = base64.b64decode(str(value)) + elif name == 'accesskey': + self.accesskey = str(value) + elif name == 'secretkey': + self.secretkey = str(value) + + +class UserRole(object): + """ + Information about a Nova user's role, as parsed through SAX. + Fields include: + role + """ + def __init__(self, connection=None): + self.connection = connection + self.role = None + + def __repr__(self): + return 'UserRole:%s' % self.role + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'role': + self.role = value + else: + setattr(self, name, str(value)) + + +class ProjectInfo(object): + """ + Information about a Nova project, as parsed through SAX + Fields include: + projectname + description + projectManagerId + memberIds + """ + + def __init__(self, connection=None): + self.connection = connection + self.projectname = None + self.description = None + self.projectManagerId = None + self.memberIds = [] + + def __repr__(self): + return 'ProjectInfo:%s' % self.projectname + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'projectname': + self.projectname = value + elif name == 'description': + self.description = value + elif name == 'projectManagerId': + self.projectManagerId = value + elif name == 'memberId': + self.memberIds.append(value) + else: + setattr(self, name, str(value)) + + +class ProjectMember(object): + """ + Information about a Nova project member, as parsed through SAX. + Fields include: + memberId + """ + + def __init__(self, connection=None): + self.connection = connection + self.memberId = None + + def __repr__(self): + return 'ProjectMember:%s' % self.memberId + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'member': + self.memberId = value + else: + setattr(self, name, str(value)) + + +class HostInfo(object): + """ + Information about a Nova Host, as parsed through SAX: + Hostname + Compute Service Status + Volume Service Status + """ + + def __init__(self, connection=None): + self.connection = connection + self.hostname = None + self.compute = None + self.volume = None + self.instance_count = 0 + self.volume_count = 0 + + def __repr__(self): + return 'Host:%s' % self.hostname + + # this is needed by the sax parser, so ignore the ugly name + def startElement(self, name, attrs, connection): + return None + + # this is needed by the sax parser, so ignore the ugly name + def endElement(self, name, value, connection): + fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) + setattr(self, fixed_name, value) + + +class Vpn(object): + """ + Information about a Vpn, as parsed through SAX + fields include: + instance_id + project_id + public_ip + public_port + created_at + internal_ip + state + """ + + def __init__(self, connection=None): + self.connection = connection + self.instance_id = None + self.project_id = None + + def __repr__(self): + return 'Vpn:%s:%s' % (self.project_id, self.instance_id) + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'instanceId': + self.instance_id = str(value) + elif name == 'projectId': + self.project_id = str(value) + elif name == 'publicIp': + self.public_ip = str(value) + elif name == 'publicPort': + self.public_port = str(value) + elif name == 'createdAt': + self.created_at = str(value) + elif name == 'internalIp': + self.internal_ip = str(value) + else: + setattr(self, name, str(value)) + + +class InstanceType(object): + """ + Information about a Nova instance type, as parsed through SAX. + + **Fields include** + + * name + * vcpus + * disk_gb + * memory_mb + * flavor_id + + """ + + def __init__(self, connection=None): + self.connection = connection + self.name = None + self.vcpus = None + self.disk_gb = None + self.memory_mb = None + self.flavor_id = None + + def __repr__(self): + return 'InstanceType:%s' % self.name + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == "memoryMb": + self.memory_mb = str(value) + elif name == "flavorId": + self.flavor_id = str(value) + elif name == "diskGb": + self.disk_gb = str(value) + else: + setattr(self, name, str(value)) + + +class StatusResponse(object): + def __init__(self, connection=None): + self.connection = connection + self.status = None + + def __repr__(self): + return 'Status:%s' % self.status + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + setattr(self, name, str(value)) + + +class NovaAdminClient(object): + def __init__(self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION, + access_key=DEFAULT_ACCESS_KEY, secret_key=DEFAULT_SECRET_KEY, + **kwargs): + parts = self.split_clc_url(clc_url) + + self.clc_url = clc_url + self.region = region + self.access = access_key + self.secret = secret_key + self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + is_secure=parts['is_secure'], + region=RegionInfo(None, + region, + parts['ip']), + port=parts['port'], + path='/services/Admin', + **kwargs) + self.apiconn.APIVersion = 'nova' + + def connection_for(self, username, project, clc_url=None, region=None, + **kwargs): + """ + Returns a boto ec2 connection for the given username. + """ + if not clc_url: + clc_url = self.clc_url + if not region: + region = self.region + parts = self.split_clc_url(clc_url) + user = self.get_user(username) + access_key = '%s:%s' % (user.accesskey, project) + return boto.connect_ec2(aws_access_key_id=access_key, + aws_secret_access_key=user.secretkey, + is_secure=parts['is_secure'], + region=RegionInfo(None, + self.region, + parts['ip']), + port=parts['port'], + path='/services/Cloud', + **kwargs) + + def split_clc_url(self, clc_url): + """ + Splits a cloud controller endpoint url. + """ + parts = httplib.urlsplit(clc_url) + is_secure = parts.scheme == 'https' + ip, port = parts.netloc.split(':') + return {'ip': ip, 'port': int(port), 'is_secure': is_secure} + + def get_users(self): + """ grabs the list of all users """ + return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) + + def get_user(self, name): + """ grab a single user by name """ + try: + return self.apiconn.get_object('DescribeUser', {'Name': name}, UserInfo) + except boto.exception.BotoServerError, e: + if e.status == 400 and e.error_code == 'NotFound': + return None + raise + + def has_user(self, username): + """ determine if user exists """ + return self.get_user(username) != None + + def create_user(self, username): + """ creates a new user, returning the userinfo object with access/secret """ + return self.apiconn.get_object('RegisterUser', {'Name': username}, UserInfo) + + def delete_user(self, username): + """ deletes a user """ + return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo) + + def get_roles(self, project_roles=True): + """Returns a list of available roles.""" + return self.apiconn.get_list('DescribeRoles', + {'ProjectRoles': project_roles}, + [('item', UserRole)]) + + def get_user_roles(self, user, project=None): + """Returns a list of roles for the given user. + Omitting project will return any global roles that the user has. + Specifying project will return only project specific roles. + """ + params = {'User':user} + if project: + params['Project'] = project + return self.apiconn.get_list('DescribeUserRoles', + params, + [('item', UserRole)]) + + def add_user_role(self, user, role, project=None): + """ + Add a role to a user either globally or for a specific project. + """ + return self.modify_user_role(user, role, project=project, + operation='add') + + def remove_user_role(self, user, role, project=None): + """ + Remove a role from a user either globally or for a specific project. + """ + return self.modify_user_role(user, role, project=project, + operation='remove') + + def modify_user_role(self, user, role, project=None, operation='add', + **kwargs): + """ + Add or remove a role for a user and project. + """ + params = {'User': user, + 'Role': role, + 'Project': project, + 'Operation': operation} + return self.apiconn.get_status('ModifyUserRole', params) + + def get_projects(self, user=None): + """ + Returns a list of all projects. + """ + if user: + params = {'User': user} + else: + params = {} + return self.apiconn.get_list('DescribeProjects', + params, + [('item', ProjectInfo)]) + + def get_project(self, name): + """ + Returns a single project with the specified name. + """ + project = self.apiconn.get_object('DescribeProject', + {'Name': name}, + ProjectInfo) + + if project.projectname != None: + return project + + def create_project(self, projectname, manager_user, description=None, + member_users=None): + """ + Creates a new project. + """ + params = {'Name': projectname, + 'ManagerUser': manager_user, + 'Description': description, + 'MemberUsers': member_users} + return self.apiconn.get_object('RegisterProject', params, ProjectInfo) + + def delete_project(self, projectname): + """ + Permanently deletes the specified project. + """ + return self.apiconn.get_object('DeregisterProject', + {'Name': projectname}, + ProjectInfo) + + def get_project_members(self, name): + """ + Returns a list of members of a project. + """ + return self.apiconn.get_list('DescribeProjectMembers', + {'Name': name}, + [('item', ProjectMember)]) + + def add_project_member(self, user, project): + """ + Adds a user to a project. + """ + return self.modify_project_member(user, project, operation='add') + + def remove_project_member(self, user, project): + """ + Removes a user from a project. + """ + return self.modify_project_member(user, project, operation='remove') + + def modify_project_member(self, user, project, operation='add'): + """ + Adds or removes a user from a project. + """ + params = {'User': user, + 'Project': project, + 'Operation': operation} + return self.apiconn.get_status('ModifyProjectMember', params) + + def get_zip(self, user, project): + """ + Returns the content of a zip file containing novarc and access credentials. + """ + params = {'Name': user, 'Project': project} + zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) + return zip.file + + def start_vpn(self, project): + """ + Starts the vpn for a user + """ + return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn) + + def get_vpns(self): + """Return a list of vpn with project name""" + return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)]) + + def get_hosts(self): + return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) + + def get_instance_types(self): + """Grabs the list of all users.""" + return self.apiconn.get_list('DescribeInstanceTypes', {}, + [('item', InstanceType)]) + + def disable_project_credentials(self, project): + """Revoke project credentials and kill the cloudpipe/vpn instance""" + return self.apiconn.get_object('DisableProjectCredentials', + {'Project': project}, StatusResponse) diff --git a/django-nova/src/django_nova/connection.py b/django-nova/src/django_nova/connection.py new file mode 100644 index 000000000..b29146ed0 --- /dev/null +++ b/django-nova/src/django_nova/connection.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Manage connections to Nova's admin API. +""" + +from django.conf import settings +from django_nova import adminclient + + +def get_nova_admin_connection(): + """ + Returns a Nova administration connection. + """ + return adminclient.NovaAdminClient ( + clc_url=settings.NOVA_DEFAULT_ENDPOINT, + region=settings.NOVA_DEFAULT_REGION, + access_key=settings.NOVA_ACCESS_KEY, + secret_key=settings.NOVA_SECRET_KEY + ) + + + diff --git a/django-nova/src/django_nova/exceptions.py b/django-nova/src/django_nova/exceptions.py new file mode 100644 index 000000000..03f3a9867 --- /dev/null +++ b/django-nova/src/django_nova/exceptions.py @@ -0,0 +1,95 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Better wrappers for errors from Nova's admin api. +""" + +import boto.exception +from django.shortcuts import redirect +from django.core import exceptions as core_exceptions + + +class NovaServerError(Exception): + """ + Consumes a BotoServerError and gives more meaningful errors. + """ + def __init__(self, ec2error): + self.status = ec2error.status + self.message = ec2error.reason + + def __str__(self): + return self.message + + +class NovaApiError(Exception): + """ + Used when Nova returns a 400 Bad Request status. + """ + def __init__(self, ec2error): + self.message = ec2error.error_message + + def __str__(self): + return self.message + + +class NovaUnavailableError(NovaServerError): + """ + Used when Nova returns a 503 Service Unavailable status. + """ + pass + + +class NovaUnauthorizedError(core_exceptions.PermissionDenied): + """ + Used when Nova returns a 401 Not Authorized status. + """ + pass + + +def wrap_nova_error(func): + """ + Used to decorate a function that interacts with boto. It will catch + and convert boto server errors and reraise as a more specific nova error. + """ + def decorator(*args, **kwargs): + try: + return func(*args, **kwargs) + except boto.exception.BotoServerError, e: + if e.status == 400 and e.error_code == 'ApiError': + raise NovaApiError(e) + elif e.status == 401: + raise NovaUnauthorizedError() + elif e.status == 503: + raise NovaUnavailableError(e) + raise NovaServerError(e) + return decorator + + +def handle_nova_error(func): + """ + Decorator for handling nova errors in a generalized way. + """ + def decorator(*args, **kwargs): + try: + return func(*args, **kwargs) + except NovaUnavailableError: + return redirect('nova_unavailable') + return decorator + + diff --git a/django-nova/src/django_nova/forms.py b/django-nova/src/django_nova/forms.py new file mode 100644 index 000000000..cc8ae2652 --- /dev/null +++ b/django-nova/src/django_nova/forms.py @@ -0,0 +1,262 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Forms used by various views. +""" + +import re + +from django import forms +from django.contrib.auth import models as auth_models +from django_nova.connection import get_nova_admin_connection +from django_nova.exceptions import wrap_nova_error + + +# TODO: Store this in settings. +MAX_VOLUME_SIZE = 100 + +alphanumeric_re = re.compile(r'^\w+$') + + +@wrap_nova_error +def get_instance_type_choices(): + """ + Returns list of instance types from nova admin api + """ + nova = get_nova_admin_connection() + instance_types = nova.get_instance_types() + rv = [] + for t in instance_types: + rv.append((t.name, "%s (%sMB memory, %s cpu, %sGB space)" % \ + (t.name, t.memory_mb, t.vcpus, t.disk_gb))) + return rv + +def get_instance_choices(project): + choices = [(i.id, i.id) for i in project.get_instances()] + if not len(choices): + choices = [('', 'none available')] + return choices + +def get_key_pair_choices(project): + choices = [(k.name, k.name) for k in project.get_key_pairs()] + if not len(choices): + choices = [('', 'none available')] + return choices + +#def get_security_group_choices(project): +# choices = [(g.name, g.description) for g in project.get_security_groups()] +# if len(choices) == 0: +# choices = [('', 'none available')] +# return choices + +def get_available_volume_choices(project): + choices = [(v.id, '%s %s - %dGB' % (v.id, v.displayName, v.size)) for v in \ + project.get_volumes() if v.status != "in-use"] + if not len(choices): + choices = [('', 'none available')] + return choices + +def get_protocols(): + return ( + ('tcp', 'tcp'), + ('udp', 'udp'), + ) + +@wrap_nova_error +def get_roles(project_roles=True): + nova = get_nova_admin_connection() + roles = nova.get_roles(project_roles=project_roles) + return [(role.role, role.role) for role in roles] + +@wrap_nova_error +def get_members(project): + nova = get_nova_admin_connection() + members = nova.get_project_members(project) + return [str(user.memberId) for user in members] + +@wrap_nova_error +def set_project_roles(projectname, username, roles): + nova = get_nova_admin_connection() + # hacky work around to interface correctly with multiple select form + _remove_roles(projectname, username) + + for role in roles: + nova.add_user_role(username, str(role), projectname) + +def _remove_roles(project, username): + nova = get_nova_admin_connection() + userroles = nova.get_user_roles(username, project) + roles = [str(role.role) for role in userroles] + + for role in roles: + if role == "developer": + nova.remove_user_role(username, "developer", project) + if role == "sysadmin": + nova.remove_user_role(username, "sysadmin", project) + if role == "netadmin": + nova.remove_user_role(username, "netadmin", project) + + +class ProjectFormBase(forms.Form): + def __init__(self, project, *args, **kwargs): + self.project = project + super(ProjectFormBase, self).__init__(*args, **kwargs) + + +class LaunchInstanceForm(forms.Form): + # nickname = forms.CharField() + # description = forms.CharField() + + count = forms.ChoiceField(choices=[(x, x) for x in range(1, 6)]) + size = forms.ChoiceField() + key_name = forms.ChoiceField() + #security_group = forms.ChoiceField() + user_data = forms.CharField(required=False, widget=forms.widgets.Textarea(attrs={'rows': 4})) + + def __init__(self, project, *args, **kwargs): + forms.Form.__init__(self, *args, **kwargs) + #self.fields['security_group'].choices = get_security_group_choices(project) + self.fields['key_name'].choices = get_key_pair_choices(project) + self.fields['size'].choices = get_instance_type_choices() + + +class UpdateInstanceForm(forms.Form): + nickname = forms.CharField(required=False, label="Name") + description = forms.CharField(required=False, widget=forms.Textarea, max_length=70) + + def __init__(self, instance, *args, **kwargs): + forms.Form.__init__(self, *args, **kwargs) + self.fields['nickname'].initial = instance.displayName + self.fields['description'].initial = instance.displayDescription + + +class UpdateImageForm(forms.Form): + nickname = forms.CharField(required=False, label="Name") + description = forms.CharField(required=False, widget=forms.Textarea, max_length=70) + + def __init__(self, image, *args, **kwargs): + forms.Form.__init__(self, *args, **kwargs) + self.fields['nickname'].initial = image.displayName + self.fields['description'].initial = image.description + + +class CreateKeyPairForm(ProjectFormBase): + name = forms.RegexField(regex=alphanumeric_re) + + def clean_name(self): + name = self.cleaned_data['name'] + + if self.project.has_key_pair(name): + raise forms.ValidationError('A key named %s already exists.' % name) + + return name + + +class CreateSecurityGroupForm(ProjectFormBase): + name = forms.RegexField(regex=alphanumeric_re) + description = forms.CharField() + + def clean_name(self): + name = self.cleaned_data['name'] + + if self.project.has_security_group(name): + raise forms.ValidationError('A security group named %s already exists.' % name) + + return name + + +class AuthorizeSecurityGroupRuleForm(forms.Form): + protocol = forms.ChoiceField(choices=get_protocols()) + from_port = forms.IntegerField(min_value=1, max_value=65535) + to_port = forms.IntegerField(min_value=1, max_value=65535) + + +class CreateVolumeForm(forms.Form): + size = forms.IntegerField(label='Size (in GB)', min_value=1, max_value=MAX_VOLUME_SIZE) + nickname = forms.CharField() + description = forms.CharField() + + +class AttachVolumeForm(ProjectFormBase): + volume = forms.ChoiceField() + instance = forms.ChoiceField() + device = forms.CharField(initial='/dev/vdb') + + def __init__(self, project, *args, **kwargs): + super(AttachVolumeForm, self).__init__(project, *args, **kwargs) + self.fields['volume'].choices = get_available_volume_choices(project) + self.fields['instance'].choices = get_instance_choices(project) + + +class ProjectForm(forms.Form): + projectname = forms.CharField(label="Project Name", max_length=20) + description = forms.CharField(label="Description", + widget=forms.widgets.Textarea()) + manager = forms.ModelChoiceField(queryset=auth_models.User.objects.all(), + label="Project Manager") + + +class GlobalRolesForm(forms.Form): + role = forms.MultipleChoiceField(label='Roles', required=False) + + def __init__(self, *args, **kwargs): + super(GlobalRolesForm, self).__init__(*args, **kwargs) + self.fields['role'].choices = get_roles(project_roles=False) + + +class ProjectUserForm(forms.Form): + role = forms.MultipleChoiceField(label='Roles', required=False) + + def __init__(self, project, user, *args, **kwargs): + super(ProjectUserForm, self).__init__(*args, **kwargs) + self.project = project + self.user = user + self.fields['role'].choices = get_roles() + + def save(self): + set_project_roles(self.project.projectname, + self.user.username, + self.cleaned_data['role']) + + +class AddProjectUserForm(forms.Form): + username = forms.ModelChoiceField(queryset='', + label='Username', + empty_label='Select a Username') + role = forms.MultipleChoiceField(label='Roles') + + def __init__(self, *args, **kwargs): + project = kwargs.pop('project') + super(AddProjectUserForm, self).__init__(*args, **kwargs) + members = get_members(project) + + self.fields['username'].queryset = \ + auth_models.User.objects.exclude(username__in=members) + self.fields['role'].choices = get_roles() + + +class SendCredentialsForm(forms.Form): + users = forms.MultipleChoiceField(label='Users', required=True) + + def __init__(self, *args, **kwargs): + query_list = kwargs.pop('query_list') + super(SendCredentialsForm, self).__init__(*args, **kwargs) + + self.fields['users'].choices = [(choices, choices) for choices in query_list] + diff --git a/local/__init__.py b/django-nova/src/django_nova/management/__init__.py similarity index 100% rename from local/__init__.py rename to django-nova/src/django_nova/management/__init__.py diff --git a/django-nova/src/django_nova/management/commands/__init__.py b/django-nova/src/django_nova/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/django-nova/src/django_nova/management/commands/createnovausers.py b/django-nova/src/django_nova/management/commands/createnovausers.py new file mode 100644 index 000000000..18bb461d8 --- /dev/null +++ b/django-nova/src/django_nova/management/commands/createnovausers.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Management commands for synchronizing the Django auth database and Nova +users database. +""" + +from django.core.management.base import NoArgsCommand +from django.contrib.auth.models import User +from django_nova.connection import get_nova_admin_connection + +class Command(NoArgsCommand): + help = 'Creates nova users for all users in the django auth database.' + + def handle_noargs(self, **options): + nova = get_nova_admin_connection() + users = User.objects.all() + for user in users: + if not nova.has_user(user.username): + self.stdout.write('creating user %s... ' % user.username) + nova.create_user(user.username) + self.stdout.write('ok\n') diff --git a/django-nova/src/django_nova/manager.py b/django-nova/src/django_nova/manager.py new file mode 100644 index 000000000..4cdb892a9 --- /dev/null +++ b/django-nova/src/django_nova/manager.py @@ -0,0 +1,340 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Simple API for interacting with Nova projects. +""" + +import boto +import boto.ec2.volume +import boto.exception +import boto.s3 +from django.conf import settings +from django_nova.connection import get_nova_admin_connection +from django_nova.exceptions import wrap_nova_error + + +class ProjectManager(object): + def __init__(self, username, project, region): + self.username = username + self.projectname = project.projectname + self.projectManagerId = project.projectManagerId + self.region = region + + def get_nova_connection(self): + """ + Returns a boto connection for a user's project. + """ + nova = get_nova_admin_connection() + return nova.connection_for(self.username, + self.projectname, + clc_url=self.region['endpoint'], + region=self.region['name']) + + def get_zip(self): + """ + Returns a buffer of a zip file containing signed credentials + for the project's Nova user. + """ + nova = get_nova_admin_connection() + return nova.get_zip(self.username, self.projectname) + + def get_images(self, image_ids=None): + conn = self.get_nova_connection() + images = conn.get_all_images(image_ids=image_ids) + sorted_images = [i for i in images if i.ownerId == self.username] + \ + [i for i in images if i.ownerId != self.username] + + return [i for i in sorted_images if i.type == 'machine' and i.location.split('/')[0] != 'nova'] + + def get_image(self, image_id): + try: + return self.get_images(image_ids=[image_id,])[0] + except IndexError: + return None + + @wrap_nova_error + def deregister_image(self, image_id): + """ + Removes the image's listing but leaves the image + and manifest in the object store in tact. + """ + conn = self.get_nova_connection() + return conn.deregister_image(image_id) + + @wrap_nova_error + def update_image(self, image_id, display_name=None, description=None): + conn = self.get_nova_connection() + params = { + 'ImageId': image_id, + 'DisplayName': display_name, + 'Description': description + } + return conn.get_object('UpdateImage', params, boto.ec2.image.Image) + + @wrap_nova_error + def modify_image_attribute(self, image_id, attribute=None, operation=None, + groups='all'): + conn = self.get_nova_connection() + return conn.modify_image_attribute(image_id, + attribute='launchPermission', + operation='remove', + groups='all',) + + + @wrap_nova_error + def run_instances(self, image_id, **kwargs): + """ + Runs instances of the specified image id. + """ + conn = self.get_nova_connection() + return conn.run_instances(image_id, **kwargs) + + def get_instance_count(self): + """ + Returns the number of active instances in this project or None if unknown. + """ + try: + return len(self.get_instances()) + except: + return None + + @wrap_nova_error + def get_instances(self): + """ + Returns all instances in this project. + """ + conn = self.get_nova_connection() + reservations = conn.get_all_instances() + instances = [] + for reservation in reservations: + for instance in reservation.instances: + instances.append(instance) + return instances + + @wrap_nova_error + def get_instance(self, instance_id): + """ + Returns detail about the specified instance. + """ + conn = self.get_nova_connection() + # TODO: Refactor this once nova's describe_instances filters by instance_id. + reservations = conn.get_all_instances() + for reservation in reservations: + for instance in reservation.instances: + if instance.id == instance_id: + return instance + return None + + @wrap_nova_error + def update_instance(self, instance_id, updates): + conn = self.get_nova_connection() + params = {'InstanceId': instance_id, 'DisplayName': updates['nickname'], + 'DisplayDescription': updates['description']} + return conn.get_object('UpdateInstance', params, + boto.ec2.instance.Instance) + + def get_instance_graph(self, region, instance_id, graph_name): + # TODO(devcamcar): Need better support for multiple regions. + # Need a way to get object store by region. + s3 = boto.s3.connection.S3Connection ( + aws_access_key_id=settings.NOVA_ACCESS_KEY, + aws_secret_access_key=settings.NOVA_SECRET_KEY, + is_secure=False, + calling_format=boto.s3.connection.OrdinaryCallingFormat(), + port=3333, + host=settings.NOVA_CLC_IP + ) + key = '_%s.monitor' % instance_id + + try: + bucket = s3.get_bucket(key, validate=False) + except boto.exception.S3ResponseError, e: + if e.code == "NoSuchBucket": + return None + else: + raise e + + key = bucket.get_key(graph_name) + + return key.read() + + @wrap_nova_error + def terminate_instance(self, instance_id): + """ Terminates the specified instance within this project. """ + conn = self.get_nova_connection() + conn.terminate_instances([instance_id]) + + @wrap_nova_error + def get_security_groups(self): + """ + Returns all security groups associated with this project. + """ + conn = self.get_nova_connection() + groups = [] + + for g in conn.get_all_security_groups(): + # Do not show vpn group. + #if g.name != 'vpn-secgroup': + groups.append(g) + + return groups + + @wrap_nova_error + def get_security_group(self, name): + """ + Returns the specified security group for this project. + """ + conn = self.get_nova_connection() + + try: + return conn.get_all_security_groups(groupnames=name.encode('ASCII'))[0] + except IndexError: + return None + + @wrap_nova_error + def has_security_group(self, name): + """ + Indicates whether a security group with the specified name exists in this project. + """ + return self.get_security_group(name) is not None + + @wrap_nova_error + def create_security_group(self, name, description): + """ + Creates a new security group in this project. + """ + conn = self.get_nova_connection() + return conn.create_security_group(name, description) + + @wrap_nova_error + def delete_security_group(self, name): + """ + Deletes a security group from the project. + """ + conn = self.get_nova_connection() + return conn.delete_security_group(name = name) + + @wrap_nova_error + def authorize_security_group(self, group_name, ip_protocol, from_port, to_port): + """ + Authorizes a rule for the specified security group. + """ + conn = self.get_nova_connection() + return conn.authorize_security_group ( + group_name = group_name, + ip_protocol = ip_protocol, + from_port = from_port, + to_port = to_port, + cidr_ip = '0.0.0.0/0' + ) + + @wrap_nova_error + def revoke_security_group(self, group_name, ip_protocol, from_port, to_port): + """ + Revokes a rule for the specified security group. + """ + conn = self.get_nova_connection() + return conn.revoke_security_group ( + group_name = group_name, + ip_protocol = ip_protocol, + from_port = from_port, + to_port = to_port, + cidr_ip = '0.0.0.0/0' + ) + + @wrap_nova_error + def get_key_pairs(self): + """ + Returns all key pairs associated with this project. + """ + conn = self.get_nova_connection() + keys = [] + + for k in conn.get_all_key_pairs(): + # Do not show vpn key. + if k.name != 'vpn-key': + keys.append(k) + + return keys + + @wrap_nova_error + def get_key_pair(self, name): + """ + Returns the specified security group for this project. + """ + conn = self.get_nova_connection() + + try: + return conn.get_all_key_pairs(keynames=name.encode('ASCII'))[0] + except IndexError: + return None + + @wrap_nova_error + def has_key_pair(self, name): + """ + Indicates whether a key pair with the specified name exists in this project. + """ + return self.get_key_pair(name) != None + + @wrap_nova_error + def create_key_pair(self, name): + """ + Creates a new key pair for this project. + """ + conn = self.get_nova_connection() + return conn.create_key_pair(name) + + @wrap_nova_error + def delete_key_pair(self, name): + """ + Deletes a new key pair from this project. + """ + conn = self.get_nova_connection() + conn.delete_key_pair(name) + + @wrap_nova_error + def get_volumes(self): + """ + Returns all volumes in this project. + """ + conn = self.get_nova_connection() + return conn.get_all_volumes() + + @wrap_nova_error + def create_volume(self, size, display_name=None, display_description=None, + snapshot=None): + conn = self.get_nova_connection() + params = {'Size': size, 'DisplayName': display_name, + 'DisplayDescription': display_description} + return conn.get_object('CreateVolume', params, boto.ec2.volume.Volume) + + @wrap_nova_error + def delete_volume(self, volume_id): + conn = self.get_nova_connection() + return conn.delete_volume(volume_id) + + @wrap_nova_error + def attach_volume(self, volume_id, instance_id, device): + conn = self.get_nova_connection() + return conn.attach_volume(volume_id, instance_id, device) + + @wrap_nova_error + def detach_volume(self, volume_id): + conn = self.get_nova_connection() + return conn.detach_volume(volume_id) + diff --git a/django-nova/src/django_nova/models.py b/django-nova/src/django_nova/models.py new file mode 100644 index 000000000..5f06f251a --- /dev/null +++ b/django-nova/src/django_nova/models.py @@ -0,0 +1,121 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Database models for authorization credentials and synchronizing Nova users. +""" + +import datetime +import random +import re +import sha +from django.conf import settings +from django.contrib.auth import models as auth_models +from django.contrib.sites import models as site_models +from django.core import mail +from django.db import models +from django.db.models.signals import post_save +from django.template.loader import render_to_string +from django_nova.connection import get_nova_admin_connection + + +SHA1_RE=re.compile('^[a-f0-9]{40}$') + + +class CredentialsAuthorization(models.Model): + username = models.CharField(max_length=128) + project = models.CharField(max_length=128) + auth_token = models.CharField(max_length=40) + auth_date = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return '%s/%s:%s' % (self.username, self.project, self.auth_token) + + @classmethod + def get_by_token(cls, token): + if SHA1_RE.search(token): + try: + credentials = cls.objects.get(auth_token=token) + except cls.DoesNotExist: + return None + if not credentials.auth_token_expired(): + return credentials + return None + + @classmethod + def authorize(cls, username, project): + return cls.objects.create(username=username, + project=project, + auth_token=cls.create_auth_token(username)) + + @staticmethod + def create_auth_token(username): + salt = sha.new(str(random.random())).hexdigest()[:5] + return sha.new(salt+username).hexdigest() + + def auth_token_expired(self): + expiration_date = datetime.timedelta(days=int(settings.CREDENTIAL_AUTHORIZATION_DAYS)) + + return self.auth_date + expiration_date <= datetime.datetime.now() + + def get_download_url(self): + return settings.CREDENTIAL_DOWNLOAD_URL + self.auth_token + + def get_zip(self): + nova = get_nova_admin_connection() + self.delete() + return nova.get_zip(self.username, self.project) + + +def credentials_post_save(sender, instance, created, *args, **kwargs): + """ + Creates a Nova User when a new Django User is created. + """ + if created: + site = site_models.Site.objects.get_current() + user = auth_models.User.objects.get(username=instance.username) + context = { + 'user': user, + 'download_url': instance.get_download_url(), + 'dashboard_url': 'http://%s/' % site.domain + } + subject = render_to_string('credentials/credentials_email_subject.txt') + body = render_to_string('credentials/credentials_email.txt', context) + + message = mail.EmailMessage(subject=subject, body=body, to=[user.email]) + message.send(fail_silently=False) +post_save.connect(credentials_post_save, + CredentialsAuthorization, + dispatch_uid='django_nova.CredentialsAuthorization.post_save') + + +def user_post_save(sender, instance, created, *args, **kwargs): + """ + Creates a Nova User when a new Django User is created. + """ + + # NOTE(devcamcar): If running unit tests, don't use a real endpoint. + if settings.NOVA_DEFAULT_ENDPOINT == 'none': + return + + if created: + nova = get_nova_admin_connection() + if not nova.has_user(instance.username): + nova.create_user(instance.username) +post_save.connect(user_post_save, + auth_models.User, + dispatch_uid='django_nova.User.post_save') diff --git a/django-nova/src/django_nova/shortcuts.py b/django-nova/src/django_nova/shortcuts.py new file mode 100644 index 000000000..15474d0b0 --- /dev/null +++ b/django-nova/src/django_nova/shortcuts.py @@ -0,0 +1,131 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Helper methods for commonly used operations. +""" + +from django.conf import settings +from django.core.cache import cache +from django.core.exceptions import PermissionDenied +from django.http import Http404 +from django_nova import manager +from django_nova.connection import get_nova_admin_connection +from django_nova.exceptions import wrap_nova_error + + +@wrap_nova_error +def get_project_or_404(request, project_id): + """ + Returns a project or 404s if it doesn't exist. + """ + + # Ensure that a connection is never attempted for a user that is unauthenticated. + if not request.user.is_authenticated: + raise PermissionDenied('User not authenticated') + + nova = get_nova_admin_connection() + project = nova.get_project(project_id) + region = get_current_region(request) + + if not project: + raise Http404('Project %s does not exist.' % project_id) + + return manager.ProjectManager(request.user, project, region) + + +@wrap_nova_error +def get_projects(user): + """ + Returns a list of projects for a user. + """ + #key = 'projects.%s' % user + #projects = cache.get(key) + + #if not projects: + # nova = get_nova_admin_connection() + # projects = nova.get_projects(user=user) + # cache.set(key, projects, 30) + + #return projects + nova = get_nova_admin_connection() + return nova.get_projects(user=user) + + +@wrap_nova_error +def get_all_regions(): + """ + Returns a list of all regions. + """ + regions = cache.get('regions') + + if not regions: + nova = get_nova_admin_connection() + conn = nova.connection_for(settings.NOVA_ADMIN_USER, settings.NOVA_PROJECT) + results = conn.get_all_regions() + regions = [{'name': r.name, 'endpoint': r.endpoint} for r in results] + cache.set('regions', regions, 60 * 60 * 24) + + return regions + + +def get_region(region_name): + regions = get_all_regions() + try: + return [r for r in regions if r['name'] == region_name][0] + except IndexError: + return None + + +def get_current_region(request): + """ + Returns the currently selected region for a user. + """ + region_name = request.session.get('region', settings.NOVA_DEFAULT_REGION) + return get_region(region_name) + + +def set_current_region(request, region_name): + """ + Sets the current region selection for a user. + """ + request.session['region'] = region_name + + +@wrap_nova_error +def get_user_image_permissions(username, project_name): + """ + Returns true if user is a sysadmin and can modify image attributes. + """ + nova = get_nova_admin_connection() + user_has_modify_permissions = False + + # checks global roles, if user is a sysadmin they can modify image attribtues. + if not user_has_modify_permissions: + for role in nova.get_user_roles(username): + if role.role == "sysadmin": + user_has_modify_permissions = True + + # checks project roles, if user is a sysadmin they can modify image attribtues. + if not user_has_modify_permissions: + for role in nova.get_user_roles(username, project_name): + if role.role == "sysadmin": + user_has_modify_permissions = True + + return user_has_modify_permissions + diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html new file mode 100644 index 000000000..e91d1889b --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html @@ -0,0 +1,45 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% load admin_modify adminmedia %} + +{% block extrahead %} +{{ block.super }} +{{ media }} +{% endblock %} + +{% block extrastyle %}{{ block.super }}{% endblock %} + +{% block coltype %}colMS{% endblock %} + +{% block bodyclass %} change-form{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ {% block object-tools %} + {% endblock %} +
+ {% csrf_token %} +
+ {% for field in form.visible_fields %} +
+ {{ field.errors }} + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+ +
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html new file mode 100644 index 000000000..4790a3ca5 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html @@ -0,0 +1,69 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% load admin_modify adminmedia %} + +{% block extrahead %} +{{ block.super }} +{{ media }} + + + + + + + + +{% endblock %} + +{% block extrastyle %}{{ block.super }}{% endblock %} + +{% block coltype %}colMS{% endblock %} + +{% block bodyclass %} change-form{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ {% block object-tools %} + {% endblock %} +
+ {% csrf_token %} +
+ + {% for field in form.visible_fields %} +
+ {{ field.errors }} + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+
+ + +
+ +
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html b/django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html new file mode 100644 index 000000000..60759c6b5 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html @@ -0,0 +1,16 @@ +{% extends "admin/change_list.html" %} + +{% block extrastyle %} + {{block.super}} + +{% endblock %} +{% block breadcrumbs %}{% endblock %} +{% block content %} +
+
+
+ {% block innercontent %}{% endblock %} +
+
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html b/django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html new file mode 100644 index 000000000..ba0046d7a --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html @@ -0,0 +1,3 @@ +{% extends "admin/change_list.html" %} +{% load admin_extras %} +{% block result_list %}{% project_result_list cl %}{% endblock %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html new file mode 100644 index 000000000..53f180efb --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html @@ -0,0 +1,25 @@ +{% extends "admin/change_list.html" %} + +{% block extrastyle %} + {{block.super}} + +{% endblock %} +{% block breadcrumbs %}{% endblock %} +{% block content %} +
+
+
+

Delete Project

+

Do you really want to delete this project?

+ + +
+ {% csrf_token %} +

+
+
+
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html new file mode 100644 index 000000000..37071ee26 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html @@ -0,0 +1,25 @@ +{% extends "admin/change_list.html" %} + +{% block extrastyle %} + {{block.super}} + +{% endblock %} +{% block breadcrumbs %}{% endblock %} +{% block content %} +
+
+
+

Remove User From Project

+

Do you really want to remove this user from project?

+ + +
+ {% csrf_token %} +

+
+
+
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html b/django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html new file mode 100644 index 000000000..6952eb451 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html @@ -0,0 +1,95 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% load admin_modify adminmedia %} + +{% block extrahead %} +{{ block.super }} +{{ media }} +{% endblock %} + +{% block extrastyle %}{{ block.super }}{% endblock %} + +{% block coltype %}colMS{% endblock %} + +{% block bodyclass %} change-form{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ {% block object-tools %} + {% endblock %} +
+ {% csrf_token %} +
+ {% for field in form.visible_fields %} +
+ {{ field.errors }} + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
+ + {{projectname}} +
+
+ + {{description}} +
+
+ + {{manager}} +
+
+
+ + {# #} +
+ +
+ + + + + + + + + + + {% for user in users %} + + + + + + {% endfor %} +
UsernameProject RolesGlobal Roles
+ {{user.memberId}} {% if user.memberId == project.projectManagerId %}(project manager){%endif %} + + + {{user.project_roles}} + + {{user.global_roles}} +
+ + +
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html new file mode 100644 index 000000000..d6d618af4 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html @@ -0,0 +1,71 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% load admin_modify adminmedia %} + +{% block extrahead %} +{{ block.super }} +{{ media }} + + + + + + + + +{% endblock %} + +{% block extrastyle %}{{ block.super }}{% endblock %} + +{% block coltype %}colMS{% endblock %} + +{% block bodyclass %} change-form{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ {% block object-tools %} + {% endblock %} +
+ {% csrf_token %} +
+
+ + {{user.username}} +
+ + {% for field in form.visible_fields %} +
+ {{ field.errors }} + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+
+ + +
+ +
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html b/django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html new file mode 100644 index 000000000..2159d7770 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html @@ -0,0 +1,42 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% block extrahead %} + {{ block.super }} +{% endblock %} +{% block innercontent %} + + + + + + + + + + + + {% for project in projects %} + + + + + + + + + {% endfor %} +
NameDescriptionProject ManagerSend CredentialsStart VPN
+ {{project.projectname}} + + {{project.description}} + + {{project.projectManagerId}} + + Send Credentials + + Start VPN +
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html new file mode 100644 index 000000000..7b19564ca --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html @@ -0,0 +1,76 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% load admin_modify adminmedia %} + +{% block extrahead %} +{{ block.super }} +{{ media }} + + + + + + + + +{% endblock %} + +{% block extrastyle %}{{ block.super }}{% endblock %} + +{% block coltype %}colMS{% endblock %} + +{% block bodyclass %} change-form{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ {% block object-tools %} + {% endblock %} +
+ {% csrf_token %} +
+
+ + {{user.username}} +
+ + {% for field in form.visible_fields %} +
+ {{ field.errors }} + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+
+ {% if project.projectManagerId != user.username %} + + {% endif %} + + +
+ +
+
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html b/django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html new file mode 100644 index 000000000..1bd0294e5 --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html @@ -0,0 +1,87 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_modify adminmedia %} + +{% block title %}Send project credentials{{ block.super }}{% endblock %} + +{% block extrahead %} +{{ block.super }} +{{ media }} + + + + + + + + + + +{% endblock %} + + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ + + {% if not success %} +

Send Credentials

+

Select which users you would like to send credentials to from the '{{ project.projectname }}' project.

+ {% else %} +

Credentials sent successfully

+ {% endif %} + +
+ {% if error %} + {{ error }} + {% endif %} + + {% if success %} + {{ success }} + {% endif %} + +
+ + {% if not success %} +
+ {% csrf_token %} +
+ + {% for field in form.visible_fields %} +
+ {{ field.errors }} + {{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+
+ +
+ +
+ {% endif %} +
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html b/django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html new file mode 100644 index 000000000..4bd59427e --- /dev/null +++ b/django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html @@ -0,0 +1,39 @@ +{% extends "admin/django_nova/project/base_projects.html" %} +{% block extrahead %} + {{ block.super }} +{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block innercontent %} +

Select a User

+ + + + + + + + + + {% for user in users %} + + + + + + {% endfor %} +
UsernameGlobal RolesActions
+ {{user.username}} + + (temporarily hidden) + {#user.roles#} + + Edit +
+{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/_messages.html b/django-nova/src/django_nova/templates/django_nova/_messages.html new file mode 100644 index 000000000..8cead94b1 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/_messages.html @@ -0,0 +1,41 @@ +{% for message in messages %} +
+ {% if message.tags == "info" %} +
+ +

+ + {{ message }} +

+
+ {% endif %} + {% if message.tags == "warning" %} +
+ +

+ + {{ message }} +

+
+ {% endif %} + {% if message.tags == "success" %} +
+ +

+ + {{ message }} +

+
+ {% endif %} + {% if message.tags == "error" %} +
+ +

+ + {{ message }} +

+
+ {% endif %} +
+{% endfor %} + diff --git a/django-nova/src/django_nova/templates/django_nova/base.html b/django-nova/src/django_nova/templates/django_nova/base.html new file mode 100644 index 000000000..d5551ae91 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/base.html @@ -0,0 +1,85 @@ +{% extends "base-sidebar.html" %} +{% load region_tags %} +{% load project_tags %} + +{% block headerjs %} +{{ block.super }} +{% endblock %} + +{% block region %} +
+ {% load_regions %} + Project: {{ project.projectname }} + +
+ {% csrf_token %} +
+ + + +
+ + +
+ +
+
+
+{% endblock %} + +{% block nav_projects %} + {% load_projects %} +
  • +

    Projects

    +
    + {% for p in projects %} +
    +

    + {{ p.projectname }} + {% if p.projectManagerId == user.username %} + Manage Project + {% endif %} +

    + {% if project.projectname == p.projectname %} + + {% endif %} +
    + {% endfor %} +
    +
  • +{% endblock %} + +{% block footerjs %} +{{ block.super }} + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/credentials/expired.html b/django-nova/src/django_nova/templates/django_nova/credentials/expired.html new file mode 100644 index 000000000..7fd741ff0 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/credentials/expired.html @@ -0,0 +1,17 @@ +{% load django_nova_tags %} + + + + + Expired Token + + +
    +

    The link you clicked has expired.

    +

    This credentials download link you have reached + is either invalid or has expired. Each link is only good for one use. If + you need to download your credentials again, please contact the + {% site_branding %} support team.

    +
    + + diff --git a/django-nova/src/django_nova/templates/django_nova/images/_launch_form.html b/django-nova/src/django_nova/templates/django_nova/images/_launch_form.html new file mode 100644 index 000000000..3d5a01e58 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/_launch_form.html @@ -0,0 +1,7 @@ +{% for field in form %} +
    + {{ field.label_tag }} + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field }} +
    +{% endfor %} diff --git a/django-nova/src/django_nova/templates/django_nova/images/_list.html b/django-nova/src/django_nova/templates/django_nova/images/_list.html new file mode 100644 index 000000000..ec25c521f --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/_list.html @@ -0,0 +1,112 @@ +

    {{ heading }}

    + {% if images %} + + + + + + + {% for image in images %} + {% if image.id == ami.id %} + + {% else %} + + + + + + {##} + + {% endif %} + {% endfor %} +
    IDDescriptionOwner
    +
    +
    +
    + Owner: + {{ ami.ownerId }} +
    + +
    + Description: + {{ ami.description }} +
    + +
    + Location: + {{ ami.location }} +
    +
    + +
    +
    + ID: + {{ ami.id }} +
    +
    + Name: + {% if ami.displayName %}{{ ami.displayName }}{%else%}{{ ami.id }}{% endif %} +
    +
    + Type: + {{ ami.type }} +
    +
    + Architecture: + {{ ami.architecture }} +
    +
    + +
    + {% if ami.is_public %} +
    Public Image
    + {% else %} +
    Private Image
    + {% endif %} + + Launch + {% if can_modify or user.username == ami.ownerId %} + Edit Image + {% endif %} + +
    + + {% if can_modify or user.username == ami.ownerId %} + +
    + {% csrf_token %} + {% if ami.is_public %} + + {% else %} + + {% endif %} +
    +
    + + +
    + {% csrf_token %} + +
    +
    + {% endif %} +
    +
    + {% if image.displayName %}{{ image.displayName }}{%else%}{{ image.id }}{% endif %} + + {% if image.description %} + {{ image.description }} + {% else %} + {{ image.location }} + {% endif %} + {{ image.ownerId }}LaunchLaunch
    + {% else %} +
    +
    +

    + + No images currently available. +

    +
    +
    + {% endif %} diff --git a/django-nova/src/django_nova/templates/django_nova/images/base.html b/django-nova/src/django_nova/templates/django_nova/images/base.html new file mode 100644 index 000000000..e96497230 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/base.html @@ -0,0 +1,7 @@ +{% extends "django_nova/base.html" %} +{% load sidebar_tags %} + +{% block nav_projects %} + {% sidebar_select images %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/django_nova/images/detail_list.html b/django-nova/src/django_nova/templates/django_nova/images/detail_list.html new file mode 100644 index 000000000..9c979e2b9 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/detail_list.html @@ -0,0 +1,207 @@ +{% extends "django_nova/images/base.html" %} + +{% block title %} - Launch an Image{% endblock %} + +{% block headerjs %} + +{% endblock %} + + +{% block content %} +
    + + + {% include "django_nova/_messages.html" %} + + {% if images %} + + + + + + + {% for image in images %} + + {% if image.id == ami.id %} + + {% else %} + + + + + {##} + {% endif %} + + {% endfor %} +
    IDDescriptionOwner
    +
    +
    +
    + Owner: + {{ ami.ownerId }} +
    + +
    + Description: + {{ ami.description }} +
    + +
    + Location: + {{ ami.location }} +
    +
    + +
    +
    + ID: + {{ ami.id }} +
    +
    + Name: + {% if ami.displayName %}{{ ami.displayName }}{%else%}{{ ami.id }}{% endif %} +
    +
    + Type: + {{ ami.type }} +
    +
    + Architecture: + {{ ami.architecture }} +
    +
    + +
    + {% if ami.is_public %} +
    Public Image
    + {% else %} +
    Private Image
    + {% endif %} + + Launch + {% if can_modify or user.username == ami.ownerId %} + Edit Image + {% endif %} + +
    + + {% if can_modify or user.username == ami.ownerId %} + +
    + {% csrf_token %} + {% if ami.is_public %} + + {% else %} + + {% endif %} +
    +
    + + +
    + {% csrf_token %} + +
    +
    + {% endif %} +
    +
    {{ image.id }} + {% if image.description %} + {{ image.description }} + {% else %} + {{ image.location }} + {% endif %} + {{ image.ownerId }}LaunchLaunch
    + {% else %} +
    +
    +

    + + No images currently available. +

    +
    +
    + {% endif %} +
    + + + +
    +

    Are you sure you wish to unregister the image?

    +
    + +{% endblock %} + +{% block footerjs %} +{{ block.super }} + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/images/edit.html b/django-nova/src/django_nova/templates/django_nova/images/edit.html new file mode 100644 index 000000000..800cfbb3c --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/edit.html @@ -0,0 +1,35 @@ +{% extends "django_nova/images/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block headerjs %} + +{% endblock %} + +{% block content %} +
    + + +
    +

    Edit Image: {{ ami.id }}

    + +
    +
    + {% csrf_token %} + {% for field in form %} + + {{ field.label_tag }} + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field }} + {% endfor %} + +
    +
    + +
    +
    + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/images/index.html b/django-nova/src/django_nova/templates/django_nova/images/index.html new file mode 100644 index 000000000..913bf0124 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/index.html @@ -0,0 +1,70 @@ +{% extends "django_nova/images/base.html" %} + +{% block title %} - Launch an Image{% endblock %} + +{% block headerjs %} + +{% endblock %} + + +{% block content %} +
    + + + {% include "django_nova/_messages.html" %} + + {% for heading, images in image_lists.items %} + {% include "django_nova/images/_list.html" %} + {% endfor %} + +
    + + +{% endblock %} + +{% block footerjs %} +{{ block.super }} + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/images/launch.html b/django-nova/src/django_nova/templates/django_nova/images/launch.html new file mode 100644 index 000000000..6e3990e76 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/images/launch.html @@ -0,0 +1,32 @@ +{% extends "django_nova/images/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block headerjs %} + +{% endblock %} + +{% block content %} +
    + + +
    +
    + {% csrf_token %} +
    +

    Launch Image {{ ami.id }}

    +
    + + {{ ami.location }} +
    + {% include "django_nova/images/_launch_form.html" %} + +
    +
    +
    +
    + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html b/django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html new file mode 100644 index 000000000..f6b9f2836 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html @@ -0,0 +1,104 @@ + {% if instances %} + + + + + + + + + {% for instance in instances %} + + {% if instance.id == selected_instance.id %} + + {% else %} + + + + + + + + {% endif %} + + {% endfor %} +
    IDImageSizeIPState
    +
    +
    +
    + Instance ID: + {{ selected_instance.id }} +
    +
    + Name: + {% if selected_instance.displayName != "" %}{{ selected_instance.displayName }}{% else %} None {% endif %} +
    +
    + Description: + {{ selected_instance.displayDescription }} +
    +
    + +
    +
    + Region: + {{ selected_instance.region.name }} +
    +
    + Size: + {{ selected_instance.instance_type }} +
    +
    + State: + {{ selected_instance.state }} +
    +
    + Image ID: + {{ selected_instance.image_id }} +
    +
    + IP Address: + {{ selected_instance.dns_name }} +
    + +
    + +
    + {% if instance.state == "running" %} + Show Console{% endif %} + Edit Instance + + +
    + + +
    + + + {% csrf_token %} +
    +
    + +
    + +
    {{ instance.id }} {% if instance.displayName %}({{ instance.displayName }}){% endif %} + {{ instance.image_id }}{{ instance.instance_type }}{{ instance.dns_name }}{{ instance.state }} +
    + + + {% csrf_token %} +
    + {% if instance.state == "running" %} + Show Console + {% endif %} +
    + {% else %} +
    +
    +

    + + No instances are currently running. You may start a new instance from the images tab. +

    +
    +
    + {% endif %} diff --git a/django-nova/src/django_nova/templates/django_nova/instances/base.html b/django-nova/src/django_nova/templates/django_nova/instances/base.html new file mode 100644 index 000000000..b793e937e --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/instances/base.html @@ -0,0 +1,7 @@ +{% extends "django_nova/base.html" %} +{% load sidebar_tags %} + +{% block nav_projects %} + {% sidebar_select instances %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/django_nova/instances/detail_list.html b/django-nova/src/django_nova/templates/django_nova/instances/detail_list.html new file mode 100644 index 000000000..a3ba87247 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/instances/detail_list.html @@ -0,0 +1,33 @@ +{% extends "django_nova/instances/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block content %} + +
    + + + {% include "django_nova/_messages.html" %} + + +
    +

    Edit Instance: {{ instance.id }}

    + +
    +
    + {% csrf_token %} + {% for field in form %} + + {{ field.label_tag }} + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field }} + {% endfor %} + +
    +
    +
    + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/instances/edit.html b/django-nova/src/django_nova/templates/django_nova/instances/edit.html new file mode 100644 index 000000000..17c987e89 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/instances/edit.html @@ -0,0 +1,34 @@ +{% extends "django_nova/instances/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block headerjs %} + +{% endblock %} + +{% block content %} +
    + + +
    +

    Edit Instance: {{ instance.id }}

    + +
    +
    + {% csrf_token %} + {% for field in update_form %} + + {{ field.label_tag }} + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field }} + {% endfor %} + +
    +
    +
    +
    + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/instances/index.html b/django-nova/src/django_nova/templates/django_nova/instances/index.html new file mode 100644 index 000000000..4774782a7 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/instances/index.html @@ -0,0 +1,98 @@ +{% extends "django_nova/instances/base.html" %} + +{% block title %} - {{ project.projectname|capfirst }} Instances{% endblock %} +{% block pageclass %}instances{% endblock %} + +{% block content %} + + + {% include "django_nova/_messages.html" %} + +
    + {% include "django_nova/instances/_instances_list.html" %} +
    + + + + +{% endblock %} + +{% block footerjs %} +{{ block.super }} + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/instances/performance.html b/django-nova/src/django_nova/templates/django_nova/instances/performance.html new file mode 100644 index 000000000..7c941ab86 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/instances/performance.html @@ -0,0 +1,58 @@ +{% extends "django_nova/instances/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block content %} + +
    + + + {% include "django_nova/_messages.html" %} + +

    +

    CPU Usage

    +

    Today

    + +

    +

    +

    This Week

    + +

    +

    +

    This Month

    + +

    + +

    +

    Network Activity

    +

    Today

    + +

    +

    +

    This Week

    + +

    +

    +

    This Month

    + +

    + +

    +

    Disk Activity

    +

    Today

    + +

    +

    +

    This Week

    + +

    +

    +

    This Month

    + +

    +
    + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html b/django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html new file mode 100644 index 000000000..d295c4064 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html @@ -0,0 +1,5 @@ +{% for field in create_form %} +{{ field.label_tag }} +{% if field.errors %}{{ field.errors }}{% endif %} +{{ field }} +{% endfor %} diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/_list.html b/django-nova/src/django_nova/templates/django_nova/keypairs/_list.html new file mode 100644 index 000000000..2447a193a --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/keypairs/_list.html @@ -0,0 +1,31 @@ + {% if keypairs %} + + + + + + + {% for keypair in keypairs %} + + + + + + {% endfor %} +
    Key Pair NameFingerprint 
    {{ keypair.name }}{{ keypair.fingerprint }} +
    + + + {% csrf_token %} +
    +
    + {% else %} +
    +
    +

    + + No key pairs currently exist. +

    +
    +
    + {% endif %} diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/base.html b/django-nova/src/django_nova/templates/django_nova/keypairs/base.html new file mode 100644 index 000000000..5fd996ca7 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/keypairs/base.html @@ -0,0 +1,7 @@ +{% extends "django_nova/base.html" %} +{% load sidebar_tags %} + +{% block nav_projects %} + {% sidebar_select keys %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/index.html b/django-nova/src/django_nova/templates/django_nova/keypairs/index.html new file mode 100644 index 000000000..2233f4813 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/keypairs/index.html @@ -0,0 +1,77 @@ +{% extends "django_nova/keypairs/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block headerjs %} +{{ block.super }} + +{% endblock %} + +{% block content %} + + + {% include "django_nova/_messages.html" %} + +
    + {% include "django_nova/keypairs/_list.html" %} +
    + +
    +
    + {% csrf_token %} + +
    +

    Create New Keypair

    + {% include "django_nova/keypairs/_create_form.html" %} + +
    +
    +
    + +
    +

    Are you sure you wish to delete key ?

    +
    +{% endblock %} + +{% block footerjs %} +{{ block.super }} + +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/projects/edit_user.html b/django-nova/src/django_nova/templates/django_nova/projects/edit_user.html new file mode 100644 index 000000000..44675ce4d --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/projects/edit_user.html @@ -0,0 +1,72 @@ +{% extends "django_nova/base.html" %} +{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %} +{% block pageclass %}overview{% endblock %} + +{% block headerjs %} +{{ block.super }} + + + + +{% endblock %} + + +{% block content %} + + + {% include "django_nova/_messages.html" %} +
    + {% if user %} + +
    + {% csrf_token %} +
    +

    Edit Roles for User: {{ user.username }}

    +
    + + {{ user.username }} +
    + + + {% for field in form.visible_fields %} +
    + {{ field.errors }} + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

    {{ field.field.help_text|safe }}

    {% endif %} +
    + {% endfor %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
    +
    + Cancel +
    +
    + + {# Remove User #} +
    +
    + {% else %} +
    +
    +

    + + No users are currently associated with this project. +

    +
    +
    + {% endif %} +
    +{% endblock %} + diff --git a/django-nova/src/django_nova/templates/django_nova/projects/index.html b/django-nova/src/django_nova/templates/django_nova/projects/index.html new file mode 100644 index 000000000..bd7b08c1c --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/projects/index.html @@ -0,0 +1,26 @@ +{% extends "django_nova/base.html" %} +{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %} +{% block pageclass %}overview{% endblock %} + +{% block content %} + + + {% include "django_nova/_messages.html" %} + +
    +

    Welcome to the {{ project.projectname|capfirst }} Overview. From here you can manage your instances, images, keys, and security groups.

    +

    To get started using the command line management tools, you can download euca2ools and use them with your x509 credentials.

    +
    + + +{% endblock %} + diff --git a/django-nova/src/django_nova/templates/django_nova/projects/manage.html b/django-nova/src/django_nova/templates/django_nova/projects/manage.html new file mode 100644 index 000000000..d7dc49687 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/projects/manage.html @@ -0,0 +1,45 @@ +{% extends "django_nova/base.html" %} +{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %} +{% block pageclass %}overview{% endblock %} + +{% block content %} + + + {% include "django_nova/_messages.html" %} + +
    + {% if members %} + + + + + + + + {% for member in members %} + + + + + + + {% endfor %} +
    UsernameProject RolesGlobal Roles 
    {{ member.memberId }} {% if project.projectManagerId == member.memberId %}(project manager){% endif %}{{ member.project_roles }}{{ member.global_roles }} + Edit +
    + {% else %} +
    +
    +

    + + No users are currently associated with this project. +

    +
    +
    + {% endif %} +
    +{% endblock %} + diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html new file mode 100644 index 000000000..80de4beef --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html @@ -0,0 +1,5 @@ +{% for field in authorize_form %} + {{ field.label_tag }} + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field }} +{% endfor %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html new file mode 100644 index 000000000..d295c4064 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html @@ -0,0 +1,5 @@ +{% for field in create_form %} +{{ field.label_tag }} +{% if field.errors %}{{ field.errors }}{% endif %} +{{ field }} +{% endfor %} diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html new file mode 100644 index 000000000..4a884cde5 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html @@ -0,0 +1,3 @@ + + + diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/base.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/base.html new file mode 100644 index 000000000..0e8a232eb --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/base.html @@ -0,0 +1,7 @@ +{% extends "django_nova/base.html" %} +{% load sidebar_tags %} + +{% block nav_projects %} + {% sidebar_select securitygroups %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html new file mode 100644 index 000000000..0e4afb9c9 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html @@ -0,0 +1,62 @@ +{% extends "django_nova/securitygroups/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block content %} +
    +
    + +
    + + + {% include "django_nova/_messages.html" %} + + + + + + + + + {% for rule in securitygroup.rules %} + + + + + + + {% endfor %} +
    ProtocolFrom PortTo Port
    {{ rule.ip_protocol }}{{ rule.from_port }}{{ rule.to_port }} +
    + {% csrf_token %} + {% include "django_nova/securitygroups/_revoke_form.html" %} + +
    +
    + +
    +

    Authorize

    +
    + {% csrf_token %} +
    + + {% include "django_nova/securitygroups/_authorize_form.html" %} + +
    +
    +
    +
    +
    +
    +
    +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/index.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/index.html new file mode 100644 index 000000000..a3c992c60 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/index.html @@ -0,0 +1,59 @@ +{% extends "django_nova/securitygroups/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block content %} +
    +
    + +
    + + + {% include "django_nova/_messages.html" %} + + + + + + + + + {% for securitygroup in securitygroups %} + + + + + + + {% endfor %} +
    NameDescriptionRules 
    {{ securitygroup.name }}{{ securitygroup.description }}{{ securitygroup.rules|length }} +
    + {% csrf_token %} + +
    +
    +
    +
    + {% csrf_token %} +
    +

    New Group

    + {% include "django_nova/securitygroups/_create_form.html" %} + +
    +
    +
    +
    +
    +
    +
    +{% endblock %} diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html b/django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html new file mode 100644 index 000000000..b473d74e7 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html @@ -0,0 +1,5 @@ +{% for field in attach_form %} +{{ field.label_tag }} +{% if field.errors %}{{ field.errors }}{% endif %} +{{ field }} +{% endfor %} diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html b/django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html new file mode 100644 index 000000000..d295c4064 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html @@ -0,0 +1,5 @@ +{% for field in create_form %} +{{ field.label_tag }} +{% if field.errors %}{{ field.errors }}{% endif %} +{{ field }} +{% endfor %} diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/base.html b/django-nova/src/django_nova/templates/django_nova/volumes/base.html new file mode 100644 index 000000000..88d1bced8 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/volumes/base.html @@ -0,0 +1,7 @@ +{% extends "django_nova/base.html" %} +{% load sidebar_tags %} + +{% block nav_projects %} + {% sidebar_select volumes %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/index.html b/django-nova/src/django_nova/templates/django_nova/volumes/index.html new file mode 100644 index 000000000..2f03a0668 --- /dev/null +++ b/django-nova/src/django_nova/templates/django_nova/volumes/index.html @@ -0,0 +1,84 @@ +{% extends "django_nova/volumes/base.html" %} + +{% block title %} - Cloud Computing{% endblock %} + +{% block content %} + + + {% include "django_nova/_messages.html" %} + + {% if volumes %} + + + + + + + {% for volume in volumes %} + + + + + {% if volume.attachment_state == "attached" %} + + {% else %} + + {% endif %} + + {% endfor %} +
    IDSizeStatus
    {{ volume.id }} {{ volume.displayName }}{{ volume.size }}GB + {% if volume.status == "in-use" %} + {% if volume.attachment_state == "attached" %} + attached: {{ volume.attach_data.instance_id }} + {% else %} + {{ volume.attachment_state }} + {% endif %} + {% else %} + {{ volume.status }} + {% endif %} + +
    + {% csrf_token %} + +
    +
    +
    + {% csrf_token %} + +
    +
    + {% else %} +
    +
    +

    + + No volumes currently exist. +

    +
    +
    + {% endif %} +
    +
    + {% csrf_token %} +
    +

    Create New Volume

    + {% include "django_nova/volumes/_create_form.html" %} + +
    +
    +
    + +
    +
    + {% csrf_token %} +
    +

    Attach Volume

    + {% include "django_nova/volumes/_attach_form.html" %} + +
    +
    +
    +{% endblock %} diff --git a/django-nova/src/django_nova/templatetags/__init__.py b/django-nova/src/django_nova/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/django-nova/src/django_nova/templatetags/admin_extras.py b/django-nova/src/django_nova/templatetags/admin_extras.py new file mode 100644 index 000000000..18e7792e2 --- /dev/null +++ b/django-nova/src/django_nova/templatetags/admin_extras.py @@ -0,0 +1,50 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Template tags for extending the Django admin interface. +""" + +from django.contrib.admin.templatetags.admin_list import items_for_result, result_headers +from django.core.urlresolvers import reverse +from django.template import Library +from django.utils.safestring import mark_safe + + +register = Library() + +def project_result_list(cl): + headers = list(result_headers(cl)) + headers.append({'text': mark_safe(' ')}) + + results = list() + + for project in cl.result_list: + rl = list(items_for_result(cl,project,None)) + + url = reverse('admin_project_sendcredentials', args=[project.projectname]) + content = mark_safe('Send Credentials' % url) + + rl.append(content) + results.append(rl) + + return { + 'cl': cl, + 'result_headers': headers, + 'results': results + } +project_result_list = register.inclusion_tag("admin/change_list_results.html")(project_result_list) diff --git a/django-nova/src/django_nova/templatetags/django_nova_tags.py b/django-nova/src/django_nova/templatetags/django_nova_tags.py new file mode 100644 index 000000000..f199af298 --- /dev/null +++ b/django-nova/src/django_nova/templatetags/django_nova_tags.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Template tags for working with django_nova. +""" + +from django import template +from django.conf import settings + + +register = template.Library() + + +class SiteBrandingNode(template.Node): + def render(self, context): + return settings.SITE_BRANDING + +@register.tag +def site_branding(parser, token): + return SiteBrandingNode() + diff --git a/django-nova/src/django_nova/templatetags/project_tags.py b/django-nova/src/django_nova/templatetags/project_tags.py new file mode 100644 index 000000000..9cdf2a67e --- /dev/null +++ b/django-nova/src/django_nova/templatetags/project_tags.py @@ -0,0 +1,39 @@ + # vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Template tags for gathering contextual region data. +""" + +from django import template +from django_nova.shortcuts import get_projects + + +register = template.Library() + + +class ProjectsNode(template.Node): + def render(self, context): + # Store project list in template context. + context['projects'] = get_projects(context['request'].user) + return '' + + +@register.tag +def load_projects(parser, token): + return ProjectsNode() diff --git a/django-nova/src/django_nova/templatetags/region_tags.py b/django-nova/src/django_nova/templatetags/region_tags.py new file mode 100644 index 000000000..9552229dd --- /dev/null +++ b/django-nova/src/django_nova/templatetags/region_tags.py @@ -0,0 +1,40 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Template tags for gathering contextual region data. +""" + +from django import template +from django_nova.shortcuts import get_current_region, get_all_regions + + +register = template.Library() + + +class RegionsNode(template.Node): + def render(self, context): + # Store region info in template context. + context['current_region'] = get_current_region(context['request']) + context['regions'] = get_all_regions() + return '' + + +@register.tag +def load_regions(parser, token): + return RegionsNode() + diff --git a/django-nova/src/django_nova/templatetags/sidebar_tags.py b/django-nova/src/django_nova/templatetags/sidebar_tags.py new file mode 100644 index 000000000..e2175725f --- /dev/null +++ b/django-nova/src/django_nova/templatetags/sidebar_tags.py @@ -0,0 +1,46 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Template tags for rendering the sidebar. +""" + +from django import template + + +register = template.Library() + + +class SidebarSelectNode(template.Node): + def __init__(self, selected): + self.selected = selected + + def render(self, context): + # Store page type in template context. + context['sidebar_selected'] = self.selected + return '' + + +@register.tag +def sidebar_select(parser, token): + try: + tag_name, selected = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0] + return SidebarSelectNode(str(selected)) + diff --git a/django-nova/src/django_nova/templatetags/truncate_filter.py b/django-nova/src/django_nova/templatetags/truncate_filter.py new file mode 100644 index 000000000..140987812 --- /dev/null +++ b/django-nova/src/django_nova/templatetags/truncate_filter.py @@ -0,0 +1,31 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Template tags for truncating strings. +""" + +from django import template + +register = template.Library() + +@register.filter("truncate") +def truncate(value, size): + if len(value) > size and size > 3: + return value[0:(size-3)] + '...' + else: + return value[0:size] diff --git a/django-nova/src/django_nova/tests/__init__.py b/django-nova/src/django_nova/tests/__init__.py new file mode 100644 index 000000000..7470671d0 --- /dev/null +++ b/django-nova/src/django_nova/tests/__init__.py @@ -0,0 +1 @@ +from view_tests import * \ No newline at end of file diff --git a/django-nova/src/django_nova/tests/templates/base-sidebar.html b/django-nova/src/django_nova/tests/templates/base-sidebar.html new file mode 100644 index 000000000..e69de29bb diff --git a/django-nova/src/django_nova/tests/urls.py b/django-nova/src/django_nova/tests/urls.py new file mode 100644 index 000000000..d85381c24 --- /dev/null +++ b/django-nova/src/django_nova/tests/urls.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +URL patterns for testing django-nova views. +""" + +from django.conf.urls.defaults import * +from django.conf.urls.defaults import * + + +urlpatterns = patterns('', + url(r'^projects/', include('django_nova.urls.project')), + url(r'^region/', include('django_nova.urls.region')), + url(r'^admin/projects/', include('django_nova.urls.admin_project')), + url(r'^admin/roles/', include('django_nova.urls.admin_roles')), + url(r'^credentials/download/(?P\w+)/$', + 'django_nova.views.credentials.authorize_credentials', + name='nova_credentials_authorize'), +) + diff --git a/django-nova/src/django_nova/tests/view_tests/__init__.py b/django-nova/src/django_nova/tests/view_tests/__init__.py new file mode 100644 index 000000000..51d01c59e --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/__init__.py @@ -0,0 +1,7 @@ +from credential_tests import * +from image_tests import * +from instance_tests import * +from keypair_tests import * +from region_tests import * +from volume_tests import * + diff --git a/django-nova/src/django_nova/tests/view_tests/base.py b/django-nova/src/django_nova/tests/view_tests/base.py new file mode 100644 index 000000000..8b394d0e7 --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/base.py @@ -0,0 +1,90 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Base classes for view based unit tests. +""" + +import mox + +from django import test +from django.conf import settings +from django.contrib.auth import models as auth_models +from django_nova import adminclient +from django_nova import manager +from django_nova import shortcuts + + +TEST_PROJECT = 'test' +TEST_USER = 'test' +TEST_REGION = 'test' + + +class BaseViewTests(test.TestCase): + def setUp(self): + self.mox = mox.Mox() + + def tearDown(self): + self.mox.UnsetStubs() + + def assertRedirectsNoFollow(self, response, expected_url): + self.assertEqual(response._headers['location'], + ('Location', settings.TESTSERVER + expected_url)) + self.assertEqual(response.status_code, 302) + + def authenticateTestUser(self): + user = auth_models.User.objects.create_user(TEST_USER, + 'test@test.com', + password='test') + login = self.client.login(username=TEST_USER, password='test') + self.failUnless(login, 'Unable to login') + return user + + +class BaseProjectViewTests(BaseViewTests): + def setUp(self): + super(BaseProjectViewTests, self).setUp() + + project = adminclient.ProjectInfo() + project.projectname = TEST_PROJECT + project.projectManagerId = TEST_USER + + self.user = self.authenticateTestUser() + self.region = adminclient.RegionInfo(name=TEST_REGION, + endpoint='http://test:8773/') + self.project = manager.ProjectManager(self.user.username, + project, + self.region) + self.mox.StubOutWithMock(shortcuts, 'get_project_or_404') + shortcuts.get_project_or_404(mox.IgnoreArg(), + 'test').AndReturn(self.project) + + def create_key_pair_choices(self, key_names): + return [(k, k) for k in key_names] + + def create_instance_type_choices(self): + return [('m1.medium', 'm1.medium'), + ('m1.large', 'm1.large')] + + def create_instance_choices(self, instance_ids): + return [(id, id) for id in instance_ids] + + def create_available_volume_choices(self, volumes): + return [(v.id, '%s %s - %dGB' % (v.id, v.displayName, v.size)) \ + for v in volumes] + diff --git a/django-nova/src/django_nova/tests/view_tests/credential_tests.py b/django-nova/src/django_nova/tests/view_tests/credential_tests.py new file mode 100644 index 000000000..5838ba91c --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/credential_tests.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for credential views. +""" + +import mox +from django.conf import settings +from django.core.urlresolvers import reverse +from django_nova import models +from django_nova.tests.view_tests.base import BaseViewTests + + +class CredentialViewTests(BaseViewTests): + def test_download_expired_credentials(self): + auth_token = 'expired' + self.mox.StubOutWithMock(models.CredentialsAuthorization, + 'get_by_token') + models.CredentialsAuthorization.get_by_token(auth_token) \ + .AndReturn(None) + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_credentials_authorize', + args=[auth_token])) + self.assertTemplateUsed(res, 'django_nova/credentials/expired.html') + + self.mox.VerifyAll() + + def test_download_good_credentials(self): + auth_token = 'good' + + creds = models.CredentialsAuthorization() + creds.username = 'test' + creds.project = 'test' + creds.auth_token = auth_token + + self.mox.StubOutWithMock(models.CredentialsAuthorization, + 'get_by_token') + self.mox.StubOutWithMock(creds, 'get_zip') + models.CredentialsAuthorization.get_by_token(auth_token) \ + .AndReturn(creds) + creds.get_zip().AndReturn('zip') + + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_credentials_authorize', + args=[auth_token])) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Disposition'], + 'attachment; filename=%s-test-test-x509.zip' % + settings.SITE_NAME) + self.assertContains(res, 'zip') + + self.mox.VerifyAll() diff --git a/django-nova/src/django_nova/tests/view_tests/image_tests.py b/django-nova/src/django_nova/tests/view_tests/image_tests.py new file mode 100644 index 000000000..7e1ee838d --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/image_tests.py @@ -0,0 +1,232 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for image views. +""" + +import boto.ec2.image +import boto.ec2.instance +import mox + +from django.core.urlresolvers import reverse +from django_nova import forms +from django_nova import shortcuts +from django_nova.tests.view_tests.base import BaseProjectViewTests, TEST_PROJECT + + +TEST_IMAGE_ID = 'ami_test' +TEST_INSTANCE_ID = 'i-abcdefg' +TEST_KEY = 'foo' + + +class ImageViewTests(BaseProjectViewTests): + def setUp(self): + self.ami = boto.ec2.image.Image() + self.ami.id = TEST_IMAGE_ID + setattr(self.ami, 'displayName', TEST_IMAGE_ID) + setattr(self.ami, 'description', TEST_IMAGE_ID) + super(ImageViewTests, self).setUp() + + def test_index(self): + self.mox.StubOutWithMock(self.project, 'get_images') + self.mox.StubOutWithMock(forms, 'get_key_pair_choices') + self.mox.StubOutWithMock(forms, 'get_instance_type_choices') + + self.project.get_images().AndReturn([]) + forms.get_key_pair_choices(self.project).AndReturn([]) + forms.get_instance_type_choices().AndReturn([]) + + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_images', args=[TEST_PROJECT])) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/images/index.html') + self.assertEqual(len(res.context['image_lists']), 3) + + self.mox.VerifyAll() + + def test_launch_form(self): + self.mox.StubOutWithMock(self.project, 'get_image') + self.mox.StubOutWithMock(forms, 'get_key_pair_choices') + self.mox.StubOutWithMock(forms, 'get_instance_type_choices') + + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + forms.get_key_pair_choices(self.project).AndReturn([]) + forms.get_instance_type_choices().AndReturn([]) + + self.mox.ReplayAll() + + args = [TEST_PROJECT, TEST_IMAGE_ID] + res = self.client.get(reverse('nova_images_launch', args=args)) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/images/launch.html') + self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID) + + self.mox.VerifyAll() + + def test_launch(self): + instance = boto.ec2.instance.Instance() + instance.id = TEST_INSTANCE_ID + instance.image_id = TEST_IMAGE_ID + reservation = boto.ec2.instance.Reservation() + reservation.instances = [instance] + + self.mox.StubOutWithMock(forms, 'get_key_pair_choices') + self.mox.StubOutWithMock(forms, 'get_instance_type_choices') + self.mox.StubOutWithMock(self.project, 'run_instances') + + forms.get_key_pair_choices(self.project).AndReturn( + self.create_key_pair_choices([TEST_KEY])) + forms.get_instance_type_choices().AndReturn( + self.create_instance_type_choices()) + self.project.run_instances(TEST_IMAGE_ID, + addressing_type=mox.IgnoreArg(), + key_name=TEST_KEY, + user_data='', + instance_type='m1.medium', + min_count='1', + max_count='1').AndReturn(reservation) + self.mox.ReplayAll() + + url = reverse('nova_images_launch', args=[TEST_PROJECT, TEST_IMAGE_ID]) + data = {'key_name': TEST_KEY, + 'count': '1', + 'size': 'm1.medium', + 'user_data': ''} + res = self.client.post(url, data) + self.assertRedirectsNoFollow(res, reverse('nova_instances', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_detail(self): + self.mox.StubOutWithMock(self.project, 'get_images') + self.mox.StubOutWithMock(self.project, 'get_image') + self.mox.StubOutWithMock(shortcuts, 'get_user_image_permissions') + self.mox.StubOutWithMock(forms, 'get_key_pair_choices') + self.mox.StubOutWithMock(forms, 'get_instance_type_choices') + + self.project.get_images().AndReturn([self.ami]) + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + shortcuts.get_user_image_permissions(mox.IgnoreArg(), + TEST_PROJECT).AndReturn(True) + forms.get_key_pair_choices(self.project).AndReturn( + self.create_key_pair_choices([TEST_KEY])) + forms.get_instance_type_choices().AndReturn( + self.create_instance_type_choices()) + + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_images_detail', + args=[TEST_PROJECT, TEST_IMAGE_ID])) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/images/index.html') + self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID) + + self.mox.VerifyAll() + + def test_remove_form(self): + self.mox.StubOutWithMock(self.project, 'get_image') + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_images_remove', + args=[TEST_PROJECT, TEST_IMAGE_ID])) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/images/detail_list.html') + self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID) + + self.mox.VerifyAll() + + def test_remove(self): + self.mox.StubOutWithMock(self.project, 'deregister_image') + self.project.deregister_image(TEST_IMAGE_ID).AndReturn(True) + self.mox.ReplayAll() + + res = self.client.post(reverse('nova_images_remove', + args=[TEST_PROJECT, TEST_IMAGE_ID])) + self.assertRedirectsNoFollow(res, reverse('nova_images', + args=[TEST_PROJECT])) + + self.mox.VerifyAll() + + def test_make_public(self): + self.mox.StubOutWithMock(self.project, 'get_image') + self.mox.StubOutWithMock(self.project, 'modify_image_attribute') + + self.ami.is_public = False + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + self.project.modify_image_attribute(TEST_IMAGE_ID, + attribute='launchPermission', + operation='add').AndReturn(True) + self.mox.ReplayAll() + + res = self.client.post(reverse('nova_images_privacy', + args=[TEST_PROJECT, TEST_IMAGE_ID])) + self.assertRedirectsNoFollow(res, reverse('nova_images_detail', + args=[TEST_PROJECT, TEST_IMAGE_ID])) + self.mox.VerifyAll() + + def test_make_private(self): + self.mox.StubOutWithMock(self.project, 'get_image') + self.mox.StubOutWithMock(self.project, 'modify_image_attribute') + + self.ami.is_public = True + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + self.project.modify_image_attribute(TEST_IMAGE_ID, + attribute='launchPermission', + operation='remove').AndReturn(True) + self.mox.ReplayAll() + + args = [TEST_PROJECT, TEST_IMAGE_ID] + res = self.client.post(reverse('nova_images_privacy', args=args)) + self.assertRedirectsNoFollow(res, reverse('nova_images_detail', + args=args)) + self.mox.VerifyAll() + + def test_update_form(self): + self.mox.StubOutWithMock(self.project, 'get_image') + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + self.mox.ReplayAll() + + args = [TEST_PROJECT, TEST_IMAGE_ID] + res = self.client.get(reverse('nova_images_update', args=args)) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/images/edit.html') + self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID) + + self.mox.VerifyAll() + + def test_update(self): + self.mox.StubOutWithMock(self.project, 'get_image') + self.mox.StubOutWithMock(self.project, 'update_image') + + self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami) + self.project.update_image(TEST_IMAGE_ID, 'test', 'test').AndReturn(True) + + self.mox.ReplayAll() + + args = [TEST_PROJECT, TEST_IMAGE_ID] + data = {'nickname': 'test', + 'description': 'test'} + url = reverse('nova_images_update', args=args) + res = self.client.post(url, data) + expected_url = reverse('nova_images_detail', args=args) + self.assertRedirectsNoFollow(res, expected_url) + + self.mox.VerifyAll() diff --git a/django-nova/src/django_nova/tests/view_tests/instance_tests.py b/django-nova/src/django_nova/tests/view_tests/instance_tests.py new file mode 100644 index 000000000..890b06405 --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/instance_tests.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for instance views. +""" + +import boto.ec2.instance +import mox + +from django.core.urlresolvers import reverse +from django_nova.tests.view_tests.base import BaseProjectViewTests, TEST_PROJECT + + +TEST_INSTANCE_ID = 'i-abcdefgh' + + +class InstanceViewTests(BaseProjectViewTests): + def test_index(self): + self.mox.StubOutWithMock(self.project, 'get_instances') + self.project.get_instances().AndReturn([]) + + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_instances', args=[TEST_PROJECT])) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/instances/index.html') + self.assertEqual(len(res.context['instances']), 0) + + self.mox.VerifyAll() + + def test_detail(self): + instance = boto.ec2.instance.Instance() + instance.id = TEST_INSTANCE_ID + instance.displayName = instance.id + instance.displayDescription = instance.id + + self.mox.StubOutWithMock(self.project, 'get_instance') + self.project.get_instance(instance.id).AndReturn(instance) + self.mox.StubOutWithMock(self.project, 'get_instances') + self.project.get_instances().AndReturn([instance]) + + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_instances_detail', + args=[TEST_PROJECT, TEST_INSTANCE_ID])) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'django_nova/instances/index.html') + self.assertEqual(res.context['selected_instance'].id, instance.id) + + self.mox.VerifyAll() + diff --git a/django-nova/src/django_nova/tests/view_tests/keypair_tests.py b/django-nova/src/django_nova/tests/view_tests/keypair_tests.py new file mode 100644 index 000000000..95085ce39 --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/keypair_tests.py @@ -0,0 +1,93 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for key pair views. +""" + +import boto.ec2.keypair +import mox + +from django.core.urlresolvers import reverse +from django_nova.tests.view_tests.base import (BaseProjectViewTests, + TEST_PROJECT) + + +TEST_KEY = 'test_key' + + +class KeyPairViewTests(BaseProjectViewTests): + def test_index(self): + self.mox.StubOutWithMock(self.project, 'get_key_pairs') + self.project.get_key_pairs().AndReturn([]) + + self.mox.ReplayAll() + + response = self.client.get(reverse('nova_keypairs', + args=[TEST_PROJECT])) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'django_nova/keypairs/index.html') + self.assertEqual(len(response.context['keypairs']), 0) + + self.mox.VerifyAll() + + def test_add_keypair(self): + key = boto.ec2.keypair.KeyPair() + key.name = TEST_KEY + + self.mox.StubOutWithMock(self.project, 'create_key_pair') + self.project.create_key_pair(key.name).AndReturn(key) + self.mox.StubOutWithMock(self.project, 'has_key_pair') + self.project.has_key_pair(key.name).AndReturn(False) + + self.mox.ReplayAll() + + url = reverse('nova_keypairs_add', args=[TEST_PROJECT]) + data = {'js': '0', 'name': key.name} + res = self.client.post(url, data) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Type'], 'application/binary') + + self.mox.VerifyAll() + + def test_delete_keypair(self): + self.mox.StubOutWithMock(self.project, 'delete_key_pair') + self.project.delete_key_pair(TEST_KEY).AndReturn(None) + + self.mox.ReplayAll() + + data = {'key_name': TEST_KEY} + url = reverse('nova_keypairs_delete', args=[TEST_PROJECT]) + res = self.client.post(url, data) + self.assertRedirectsNoFollow(res, reverse('nova_keypairs', + args=[TEST_PROJECT])) + + self.mox.VerifyAll() + + def test_download_keypair(self): + material = 'abcdefgh' + session = self.client.session + session['key.%s' % TEST_KEY] = material + session.save() + + res = self.client.get(reverse('nova_keypairs_download', + args=['test', TEST_KEY])) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Type'], 'application/binary') + self.assertContains(res, material) + diff --git a/django-nova/src/django_nova/tests/view_tests/region_tests.py b/django-nova/src/django_nova/tests/view_tests/region_tests.py new file mode 100644 index 000000000..6e4171ffe --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/region_tests.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for region views. +""" + +from django.core.urlresolvers import reverse +from django_nova.tests.view_tests.base import BaseViewTests +from django_nova import shortcuts + + +TEST_REGION = 'one' + + +class RegionViewTests(BaseViewTests): + def test_change(self): + self.authenticateTestUser() + session = self.client.session + session['region'] = 'two' + session.save() + + data = {'redirect_url': '/', + 'region': TEST_REGION} + res = self.client.post(reverse('region_change'), data) + self.assertEqual(self.client.session['region'], TEST_REGION) + self.assertRedirectsNoFollow(res, '/') + diff --git a/django-nova/src/django_nova/tests/view_tests/volume_tests.py b/django-nova/src/django_nova/tests/view_tests/volume_tests.py new file mode 100644 index 000000000..cd9174060 --- /dev/null +++ b/django-nova/src/django_nova/tests/view_tests/volume_tests.py @@ -0,0 +1,170 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Unit tests for volume views. +""" + +import boto.ec2.volume +import mox + +from django.core.urlresolvers import reverse +from django_nova import forms +from django_nova.tests.view_tests.base import (BaseProjectViewTests, + TEST_PROJECT) + + +TEST_VOLUME = 'vol-0000001' + + +class VolumeTests(BaseProjectViewTests): + def test_index(self): + instance_id = 'i-abcdefgh' + + volume = boto.ec2.volume.Volume() + volume.id = TEST_VOLUME + volume.displayName = TEST_VOLUME + volume.size = 1 + + self.mox.StubOutWithMock(self.project, 'get_volumes') + self.mox.StubOutWithMock(forms, 'get_available_volume_choices') + self.mox.StubOutWithMock(forms, 'get_instance_choices') + self.project.get_volumes().AndReturn([]) + forms.get_available_volume_choices(mox.IgnoreArg()).AndReturn( + self.create_available_volume_choices([volume])) + forms.get_instance_choices(mox.IgnoreArg()).AndReturn( + self.create_instance_choices([instance_id])) + + self.mox.ReplayAll() + + response = self.client.get(reverse('nova_volumes', + args=[TEST_PROJECT])) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'django_nova/volumes/index.html') + self.assertEqual(len(response.context['volumes']), 0) + + self.mox.VerifyAll() + + def test_add_get(self): + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_volumes_add', args=[TEST_PROJECT])) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_add_post(self): + vol = boto.ec2.volume.Volume() + vol.name = TEST_VOLUME + vol.displayName = TEST_VOLUME + vol.size = 1 + + self.mox.StubOutWithMock(self.project, 'create_volume') + self.project.create_volume(vol.size, vol.name, vol.name).AndReturn(vol) + + self.mox.ReplayAll() + + url = reverse('nova_volumes_add', args=[TEST_PROJECT]) + data = {'size': '1', + 'nickname': TEST_VOLUME, + 'description': TEST_VOLUME} + res = self.client.post(url, data) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_delete_get(self): + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_volumes_delete', + args=[TEST_PROJECT, TEST_VOLUME])) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_delete_post(self): + self.mox.StubOutWithMock(self.project, 'delete_volume') + self.project.delete_volume(TEST_VOLUME).AndReturn(True) + + self.mox.ReplayAll() + + res = self.client.post(reverse('nova_volumes_delete', + args=[TEST_PROJECT, TEST_VOLUME])) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_attach_get(self): + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_volumes_attach', + args=[TEST_PROJECT])) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_attach_post(self): + volume = boto.ec2.volume.Volume() + volume.id = TEST_VOLUME + volume.displayName = TEST_VOLUME + volume.size = 1 + + instance_id = 'i-abcdefgh' + device = '/dev/vdb' + + self.mox.StubOutWithMock(self.project, 'attach_volume') + self.mox.StubOutWithMock(forms, 'get_available_volume_choices') + self.mox.StubOutWithMock(forms, 'get_instance_choices') + self.project.attach_volume(TEST_VOLUME, instance_id, device) \ + .AndReturn(True) + forms.get_available_volume_choices(mox.IgnoreArg()).AndReturn( + self.create_available_volume_choices([volume])) + forms.get_instance_choices(mox.IgnoreArg()).AndReturn( + self.create_instance_choices([instance_id])) + + self.mox.ReplayAll() + + url = reverse('nova_volumes_attach', args=[TEST_PROJECT]) + data = {'volume': TEST_VOLUME, + 'instance': instance_id, + 'device': device} + res = self.client.post(url, data) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_detach_get(self): + self.mox.ReplayAll() + + res = self.client.get(reverse('nova_volumes_detach', + args=[TEST_PROJECT, TEST_VOLUME])) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() + + def test_detach_post(self): + self.mox.StubOutWithMock(self.project, 'detach_volume') + self.project.detach_volume(TEST_VOLUME).AndReturn(True) + + self.mox.ReplayAll() + + res = self.client.post(reverse('nova_volumes_detach', + args=[TEST_PROJECT, TEST_VOLUME])) + self.assertRedirectsNoFollow(res, reverse('nova_volumes', + args=[TEST_PROJECT])) + self.mox.VerifyAll() diff --git a/django-nova/src/django_nova/testsettings.py b/django-nova/src/django_nova/testsettings.py new file mode 100644 index 000000000..80eb224fc --- /dev/null +++ b/django-nova/src/django_nova/testsettings.py @@ -0,0 +1,21 @@ +import os + +ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) +DEBUG = True +TESTSERVER = 'http://testserver' +DATABASE_ENGINE = 'sqlite3' +DATABASE_NAME = '/tmp/django-nova.db' +INSTALLED_APPS = ['django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django_nova'] +ROOT_URLCONF = 'django_nova.tests.urls' +TEMPLATE_DIRS = ( + os.path.join(ROOT_PATH, 'tests', 'templates') +) +SITE_BRANDING = 'OpenStack' +SITE_NAME = 'openstack' +NOVA_DEFAULT_ENDPOINT = 'none' +NOVA_DEFAULT_REGION = 'test' +NOVA_ACCESS_KEY = 'test' +NOVA_SECRET_KEY = 'test' diff --git a/django-nova/src/django_nova/urls/__init__.py b/django-nova/src/django_nova/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/django-nova/src/django_nova/urls/admin_project.py b/django-nova/src/django_nova/urls/admin_project.py new file mode 100644 index 000000000..c32e2adbe --- /dev/null +++ b/django-nova/src/django_nova/urls/admin_project.py @@ -0,0 +1,55 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +URL patterns for managing Nova projects through the Django admin interface. +""" + +from django.conf.urls.defaults import * + + +#TODO(devcamcar): Standardize url names admin_project_*. + +urlpatterns = patterns('', + url(r'^$', + 'django_nova.views.admin.projects_list', + name='admin_projects'), + url(r'^add/$', + 'django_nova.views.admin.add_project', + name='add_project'), + url(r'^(?P[^/]+)/$', + 'django_nova.views.admin.project_view', + name='admin_project'), + url(r'^(?P[^/]+)/user/(?P[^/]+)/delete/', + 'django_nova.views.admin.delete_project_user', + name='admin_project_delete_user'), + url(r'^(?P[^/]+)/delete/$', + 'django_nova.views.admin.delete_project', + name='delete_project'), + url(r'^(?P[^/]+)/user/add/$', + 'django_nova.views.admin.add_project_user', + name='add_project_user'), + url(r'^(?P[^/]+)/user/(?P[^/]+)/$', + 'django_nova.views.admin.project_user', + name='project_user'), + url(r'^(?P[^/]+)/sendcredentials/$', + 'django_nova.views.admin.project_sendcredentials', + name='admin_project_sendcredentials'), + url(r'^(?P[^/]+)/start_vpn/$', + 'django_nova.views.admin.project_start_vpn', + name='admin_project_start_vpn'), +) diff --git a/django-nova/src/django_nova/urls/admin_roles.py b/django-nova/src/django_nova/urls/admin_roles.py new file mode 100644 index 000000000..d23380bc4 --- /dev/null +++ b/django-nova/src/django_nova/urls/admin_roles.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +URL patterns for managing Nova user roles through the Django admin interface. +""" + +from django.conf.urls.defaults import * + + +urlpatterns = patterns('', + url(r'^(?P[^/]+)/$', + 'django_nova.views.admin.user_roles', + name='admin_user_roles'), + url(r'^$', + 'django_nova.views.admin.users_list', + name='admin_users_list'), +) diff --git a/django-nova/src/django_nova/urls/project.py b/django-nova/src/django_nova/urls/project.py new file mode 100644 index 000000000..a5cc6056d --- /dev/null +++ b/django-nova/src/django_nova/urls/project.py @@ -0,0 +1,129 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +URL patterns for managing Nova projects. +""" + +from django.conf.urls.defaults import * + + +urlpatterns = patterns('', + url(r'^(?P[^/]+)/$', + 'django_nova.views.projects.detail', + name='nova_project'), + url(r'^(?P[^/]+)/manage/(?P[^/]+)/', + 'django_nova.views.projects.edit_user', + name='nova_project_edit_user'), + url(r'^(?P[^/]+)/manage$', + 'django_nova.views.projects.manage', + name='nova_project_manage'), + url(r'^(?P[^/]+)/download/credentials$', + 'django_nova.views.projects.download_credentials', + name='nova_download_credentials'), + url(r'^(?P[^/]+)/images$', + 'django_nova.views.images.index', + name='nova_images'), + url(r'^(?P[^/]+)/images/(?P[^/]+)/launch$', + 'django_nova.views.images.launch', + name='nova_images_launch'), + url(r'^(?P[^/]+)/images/(?P[^/]+)/remove$', + 'django_nova.views.images.remove', + name='nova_images_remove'), + url(r'^(?P[^/]+)/images/(?P[^/]+)/update$', + 'django_nova.views.images.update', + name='nova_images_update'), + url(r'^(?P[^/]+)/images/(?P[^/]+)/detail$', + 'django_nova.views.images.detail', + name='nova_images_detail'), + url(r'^(?P[^/]+)/images/(?P[^/]+)$', + 'django_nova.views.images.privacy', + name='nova_images_privacy'), + url(r'^(?P[^/]+)/instances$', + 'django_nova.views.instances.index', + name='nova_instances'), + url(r'^(?P[^/]+)/instances/refresh$', + 'django_nova.views.instances.refresh', + name='nova_instances_refresh'), + url(r'^(?P[^/]+)/instances/(?P[^/]+)/refresh$', + 'django_nova.views.instances.refresh_detail', + name='nova_instances_refresh_detail'), + url(r'^(?P[^/]+)/instances/terminate$', + 'django_nova.views.instances.terminate', + name='nova_instances_terminate'), + url(r'^(?P[^/]+)/instances/(?P[^/]+)$', + 'django_nova.views.instances.detail', + name='nova_instances_detail'), + url(r'^(?P[^/]+)/instances/(?P[^/]+)/performance$', + 'django_nova.views.instances.performance', + name='nova_instances_performance'), + url(r'^(?P[^/]+)/instances/(?P[^/]+)/console$', + 'django_nova.views.instances.console', + name='nova_instances_console'), + url(r'^(?P[^/]+)/instances/(?P.*)/update$', + 'django_nova.views.instances.update', + name='nova_instance_update'), + url(r'^(?P[^/]+)/instances/(?P[^/]+)/graph/(?P[^/]+)$', + 'django_nova.views.instances.graph', + name='nova_instances_graph'), + url(r'^(?P[^/]+)/keys$', + 'django_nova.views.keypairs.index', + name='nova_keypairs'), + url(r'^(?P[^/]+)/keys/add$', + 'django_nova.views.keypairs.add', + name='nova_keypairs_add'), + url(r'^(?P[^/]+)/keys/delete$', + 'django_nova.views.keypairs.delete', + name='nova_keypairs_delete'), + url(r'^(?P[^/]+)/keys/(?P.*)/download$', + 'django_nova.views.keypairs.download', + name='nova_keypairs_download'), + #url(r'^(?P[^/]+)/securitygroups/$', + # 'django_nova.views.securitygroups.index', + # name='nova_securitygroups'), + #url(r'^(?P[^/]+)/securitygroups/add$', + # 'django_nova.views.securitygroups.add', + # name='nova_securitygroups_add'), + #url(r'^(?P[^/]+)/securitygroups/(?P[^/]+)$', + # 'django_nova.views.securitygroups.detail', + # name='nova_securitygroups_detail'), + #url(r'^(?P[^/]+)/securitygroups/(?P[^/]+)/authorize/$', + # 'django_nova.views.securitygroups.authorize', + # name='nova_securitygroups_authorize'), + #url(r'^(?P[^/]+)/securitygroups/(?P[^/]+)/delete/$', + # 'django_nova.views.securitygroups.delete', + # name='nova_securitygroups_delete'), + #url(r'^(?P[^/]+)/securitygroups/(?P.*)/revoke/$', + # 'django_nova.views.securitygroups.revoke', + # name='nova_securitygroups_revoke'), + url(r'^(?P[^/]+)/volumes/$', + 'django_nova.views.volumes.index', + name='nova_volumes'), + url(r'^(?P[^/]+)/volumes/add$', + 'django_nova.views.volumes.add', + name='nova_volumes_add'), + url(r'^(?P[^/]+)/volumes/attach$', + 'django_nova.views.volumes.attach', + name='nova_volumes_attach'), + url(r'^(?P[^/]+)/volumes/(?P[^/]+)/detach$', + 'django_nova.views.volumes.detach', + name='nova_volumes_detach'), + url(r'^(?P[^/]+)/volumes/(?P[^/]+)/delete$', + 'django_nova.views.volumes.delete', + name='nova_volumes_delete'), +) diff --git a/django-nova/src/django_nova/urls/region.py b/django-nova/src/django_nova/urls/region.py new file mode 100644 index 000000000..dde1e01bc --- /dev/null +++ b/django-nova/src/django_nova/urls/region.py @@ -0,0 +1,29 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +URL patterns for managing Nova regions. +""" + +from django.conf.urls.defaults import * + + +urlpatterns = patterns('', + url(r'^change/$', + 'django_nova.views.regions.change', + name='region_change'), +) diff --git a/django-nova/src/django_nova/views/__init__.py b/django-nova/src/django_nova/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/django-nova/src/django_nova/views/admin.py b/django-nova/src/django_nova/views/admin.py new file mode 100644 index 000000000..ec1b43904 --- /dev/null +++ b/django-nova/src/django_nova/views/admin.py @@ -0,0 +1,326 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova through the Django admin interface. +""" + +import boto.exception +from django import http +from django import template +from django.contrib import messages +from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.auth import models as auth_models +from django.shortcuts import redirect, render_to_response +from django_nova import forms +from django_nova import models +from django_nova.connection import get_nova_admin_connection + + +@staff_member_required +def project_sendcredentials(request, project_id): + nova = get_nova_admin_connection() + project = nova.get_project(project_id) + + users = [user.memberId for user in nova.get_project_members(project_id)] + form = forms.SendCredentialsForm(query_list=users) + + if project == None: + raise http.Http404() + + if request.method == 'POST': + if len(request.POST.getlist('users')) < 1: + msg = "Please select a user to send credentials to." + + return render_to_response('admin/django_nova/project/send_credentials.html', { + 'project' : project, + 'form' : form, + 'users' : users, + 'error': msg, + }, context_instance = template.RequestContext(request)) + else: + for username in request.POST.getlist('users'): + models.CredentialsAuthorization.authorize(username, project_id) + msg = "Credentials were successfully sent." + return render_to_response('admin/django_nova/project/send_credentials.html', { + 'project' : project, + 'form' : form, + 'users' : users, + 'success': msg, + }, context_instance = template.RequestContext(request)) + + return render_to_response('admin/django_nova/project/send_credentials.html', { + 'project' : project, + 'form' : form, + 'users' : users, + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def project_start_vpn(request, project_id): + nova = get_nova_admin_connection() + project = nova.get_project(project_id) + + if project == None: + raise http.Http404() + + try: + nova.start_vpn(project_id) + messages.success(request, + 'Successfully started VPN for project %s.' % + project_id) + except boto.exception.EC2ResponseError, e: + messages.error(request, + 'Unable to start VPN for the project %s: %s - %s' % + (project_id, e.code, e.error_message)) + + return redirect('admin_projects') + + +@staff_member_required +def projects_list(request): + nova = get_nova_admin_connection() + projects = nova.get_projects() + + return render_to_response('admin/django_nova/project/project_list.html', { + 'projects' : projects + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def project_view(request, project_name): + nova = get_nova_admin_connection() + project = nova.get_project(project_name) + users = nova.get_project_members(project_name) + try: + manager = auth_models.User.objects.get(username=project.projectManagerId) + except auth_models.User.DoesNotExist: + manager = None + + for user in users: + project_role = [str(role.role) for role in nova.get_user_roles(user.memberId, project_name)] + global_role = [str(role.role) for role in nova.get_user_roles(user.memberId, project=False)] + + user.project_roles = ", ".join(project_role) + user.global_roles = ", ".join(global_role) + + return render_to_response('admin/django_nova/project/edit_project.html', { + 'project' : project, + 'users' : users, + 'projectname': project.projectname, + 'manager': manager, + 'description': project.description, + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def add_project(request): + nova = get_nova_admin_connection() + + if request.method == 'POST': + form = forms.ProjectForm(request.POST) + if form.is_valid(): + manager = form.cleaned_data["manager"] + nova.create_project(form.cleaned_data["projectname"], + manager.username, + form.cleaned_data["description"]) + return redirect('admin_project', request.POST["projectname"]) + else: + form = forms.ProjectForm() + + return render_to_response('admin/django_nova/project/add_project.html', { + 'form' : form, + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def delete_project(request, project_name): + nova = get_nova_admin_connection() + + if request.method == 'POST': + nova.delete_project(project_name) + return redirect('admin_projects') + + project = nova.get_project(project_name) + + return render_to_response('admin/django_nova/project/delete_project.html', { + 'project' : project, + }, context_instance = template.RequestContext(request)) + +def remove_project_roles(username, project): + nova = get_nova_admin_connection() + userroles = nova.get_user_roles(username, project) + roles = [str(role.role) for role in userroles] + + for role in roles: + if role == "developer": + nova.remove_user_role(username, "developer", project) + if role == "sysadmin": + nova.remove_user_role(username, "sysadmin", project) + if role == "netadmin": + nova.remove_user_role(username, "netadmin", project) + +def remove_global_roles(username): + nova = get_nova_admin_connection() + userroles = nova.get_user_roles(username) + roles = [str(role.role) for role in userroles] + + for role in roles: + if role == "developer": + nova.remove_user_role(username, "developer") + if role == "sysadmin": + nova.remove_user_role(username, "sysadmin") + if role == "netadmin": + nova.remove_user_role(username, "netadmin") + if role == "cloudadmin": + nova.remove_user_role(username, "cloudadmin") + if role == "itsec": + nova.remove_user_role(username, "itsec") + + +@staff_member_required +def project_user(request, project_name, project_user): + nova = get_nova_admin_connection() + userroles = nova.get_user_roles(project_user, project_name) + try: + modeluser = auth_models.User.objects.get(username = project_user) + except auth_models.User.DoesNotExist: + modeluser = None + + if request.method == 'POST': + form = forms.ProjectUserForm(request.POST) + if form.is_valid(): + username = project_user + + # hacky work around to interface correctly with multiple select form + remove_project_roles(username, project_name) + + roleform = request.POST.getlist("role") + for role in roleform: + nova.add_user_role(username, str(role), project_name) + + return redirect('admin_project', project_name) + else: + roles = [str(role.role) for role in userroles] + form = forms.ProjectUserForm({ + 'role': roles, + 'user': modeluser, + }) + + project = nova.get_project(project_name) + + return render_to_response('admin/django_nova/project/project_user.html', { + 'form' : form, + 'project' : project, + 'user': modeluser, + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def add_project_user(request, project_name): + nova = get_nova_admin_connection() + + if request.method == 'POST': + form = forms.AddProjectUserForm(request.POST, project=project_name) + if form.is_valid(): + username = form.cleaned_data["username"].username + nova.add_project_member(username, project_name,) + + roleform = request.POST.getlist("role") + for role in roleform: + nova.add_user_role(username, str(role), project_name) + + return redirect('admin_project', project_name) + else: + form = forms.AddProjectUserForm(project=project_name) + + project = nova.get_project(project_name) + + return render_to_response('admin/django_nova/project/add_project_user.html', { + 'form' : form, + 'project' : project, + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def delete_project_user(request, project_name, project_user): + nova = get_nova_admin_connection() + + if request.method == 'POST': + nova.remove_project_member(project_user, project_name) + return redirect('admin_project', project_name) + + project = nova.get_project(project_name) + user = nova.get_user(project_user) + + return render_to_response('admin/django_nova/project/delete_project_user.html', { + 'user' : user, + 'project' : project, + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def users_list(request): + nova = get_nova_admin_connection() + users = nova.get_users() + + for user in users: + # NOTE(devcamcar): Temporarily disabled for performance reasons. + #roles = [str(role.role) for role in nova.get_user_roles(user.username)] + roles = [] + user.roles = ", ".join(roles) + + return render_to_response('admin/django_nova/project/user_list.html', { + 'users' : users + }, context_instance = template.RequestContext(request)) + + +@staff_member_required +def user_roles(request, user_id): + nova = get_nova_admin_connection() + userroles = nova.get_user_roles(user_id) + try: + modeluser = auth_models.User.objects.get(username=user_id) + except auth_models.User.DoesNotExist: + modeluser = None + + if request.method == 'POST': + form = forms.GlobalRolesForm(request.POST) + if form.is_valid(): + username = user_id + + # hacky work around to interface correctly with multiple select form + remove_global_roles(username) + + roleform = request.POST.getlist("role") + for role in roleform: + nova.add_user_role(username, str(role)) + + return redirect('admin_user_roles', user_id) + else: + roles = [str(role.role) for role in userroles] + form = forms.GlobalRolesForm({ + 'username': modeluser and modeluser.id or None, + 'role': roles, + }) + + return render_to_response('admin/django_nova/project/global_edit_user.html', { + 'form' : form, + 'user' : modeluser, + }, context_instance = template.RequestContext(request)) + diff --git a/django-nova/src/django_nova/views/credentials.py b/django-nova/src/django_nova/views/credentials.py new file mode 100644 index 000000000..3626e0279 --- /dev/null +++ b/django-nova/src/django_nova/views/credentials.py @@ -0,0 +1,46 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for downloading X509 credentials. Useful when using an invitation +style system for configuring first time users. +""" + +from django import http +from django.conf import settings +from django.shortcuts import render_to_response +from django_nova import models + + +def authorize_credentials(request, auth_token): + """Sends X509 credentials to user if their auth token is valid.""" + auth_token = auth_token.lower() + credentials = models.CredentialsAuthorization.get_by_token(auth_token) + + # NOTE(devcamcar): If nothing returned, then token was bad or has expired. + if not credentials: + return render_to_response('django_nova/credentials/expired.html') + + response = http.HttpResponse(mimetype='application/zip') + response['Content-Disposition'] = \ + 'attachment; filename=%s-%s-%s-x509.zip' % \ + (settings.SITE_NAME, credentials.project, credentials.username) + response.write(credentials.get_zip()) + + return response + diff --git a/django-nova/src/django_nova/views/images.py b/django-nova/src/django_nova/views/images.py new file mode 100644 index 000000000..1d09d2dfd --- /dev/null +++ b/django-nova/src/django_nova/views/images.py @@ -0,0 +1,229 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova images. +""" + +import boto.exception +import re + +from django import http +from django import template +from django.conf import settings +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render_to_response +from django_nova import exceptions +from django_nova import forms +from django_nova import shortcuts +from django_nova.exceptions import handle_nova_error + + +def _image_lists(images, project_id): + def image_is_project(i): + return i.ownerId == project_id + + def image_is_admin(i): + return i.ownerId in ['admin'] + + def image_is_community(i): + return (not image_is_admin(i)) and (not image_is_project(i)) + + return {'Project Images': filter(image_is_project, images), + '%s Images' % settings.SITE_BRANDING: filter(image_is_admin, + images), + 'Community Images': filter(image_is_community, images)} + + +@login_required +@handle_nova_error +def index(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + images = project.get_images() + + return render_to_response('django_nova/images/index.html', { + 'form': forms.LaunchInstanceForm(project), + 'region': project.region, + 'project': project, + 'image_lists': _image_lists(images, project_id), + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def launch(request, project_id, image_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + form = forms.LaunchInstanceForm(project, request.POST) + if form.is_valid(): + try: + reservation = project.run_instances( + image_id, + addressing_type='private', + key_name=form.cleaned_data['key_name'], + #security_groups=[form.cleaned_data['security_group']], + user_data=re.sub('\r\n', '\n', + form.cleaned_data['user_data']), + instance_type=form.cleaned_data['size'], + min_count=form.cleaned_data['count'], + max_count=form.cleaned_data['count'] + ) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to launch: %s' % e.message) + else: + for instance in reservation.instances: + messages.success(request, + 'Instance %s launched.' % instance.id) + return redirect('nova_instances', project_id) + else: + form = forms.LaunchInstanceForm(project) + + ami = project.get_image(image_id) + + return render_to_response('django_nova/images/launch.html', { + 'form': form, + 'region': project.region, + 'project': project, + 'ami': ami, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def detail(request, project_id, image_id): + project = shortcuts.get_project_or_404(request, project_id) + images = project.get_images() + + ami = project.get_image(image_id) + can_modify = shortcuts.get_user_image_permissions(request.user.username, + project_id) + + if not ami: + raise http.Http404() + return render_to_response('django_nova/images/index.html', { + 'form': forms.LaunchInstanceForm(project), + 'region': project.region, + 'project': project, + 'images': images, + 'image_lists': _image_lists(images, project_id), + 'ami': ami, + 'can_modify': can_modify, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def remove(request, project_id, image_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + try: + project.deregister_image(image_id) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to deregister image: %s' % e.message) + else: + messages.success(request, + 'Image %s has been successfully deregistered.' % + image_id) + + return redirect('nova_images', project_id) + else: + ami = project.get_image(image_id) + + #FIXME - is the code below used? if we reach here should we + #just redirect? (anthony) + return render_to_response('django_nova/images/detail_list.html', { + 'region': project.region, + 'project': project, + 'ami': ami, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def privacy(request, project_id, image_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + ami = project.get_image(image_id) + + if ami.is_public: + try: + project.modify_image_attribute(image_id, + attribute='launchPermission', + operation='remove') + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to make image private: %s' % e.message) + else: + try: + project.modify_image_attribute(image_id, + attribute='launchPermission', + operation='add') + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to make image public: %s' % e.message) + + return redirect('nova_images_detail', project_id, image_id) + + +@login_required +@handle_nova_error +def update(request, project_id, image_id): + project = shortcuts.get_project_or_404(request, project_id) + ami = project.get_image(image_id) + + if request.method == 'POST': + form = forms.UpdateImageForm(ami, request.POST) + if form.is_valid(): + try: + project.update_image(image_id, + form.cleaned_data['nickname'], + form.cleaned_data['description']) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to update image: %s' % e.message) + else: + messages.success(request, + 'Image %s has been updated.' % image_id) + + return redirect('nova_images_detail', project_id, image_id) + + # TODO(devcamcar): This needs to be cleaned up. Can make + # one of the render_to_response blocks go away. + else: + form = forms.UpdateImageForm(ami) + return render_to_response('django_nova/images/edit.html', { + 'form': form, + 'region': project.region, + 'project': project, + 'ami': ami, + }, context_instance = template.RequestContext(request)) + else: + form = forms.UpdateImageForm(ami) + return render_to_response('django_nova/images/edit.html', { + 'form': form, + 'region': project.region, + 'project': project, + 'ami': ami, + }, context_instance = template.RequestContext(request)) + diff --git a/django-nova/src/django_nova/views/instances.py b/django-nova/src/django_nova/views/instances.py new file mode 100644 index 000000000..6fe2e0d01 --- /dev/null +++ b/django-nova/src/django_nova/views/instances.py @@ -0,0 +1,203 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova instances. +""" + +from django import http +from django import template +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render_to_response +from django_nova import exceptions +from django_nova import forms as nova_forms +from django_nova import shortcuts +from django_nova.exceptions import handle_nova_error + + +@login_required +@handle_nova_error +def index(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name) + + return render_to_response('django_nova/instances/index.html', { + 'region': project.region, + 'project': project, + 'instances': instances, + 'detail' : False, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def detail(request, project_id, instance_id): + project = shortcuts.get_project_or_404(request, project_id) + instance = project.get_instance(instance_id) + instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name) + + if not instance: + raise http.Http404() + + return render_to_response('django_nova/instances/index.html', { + 'region': project.region, + 'project': project, + 'selected_instance': instance, + 'instances': instances, + 'update_form': nova_forms.UpdateInstanceForm(instance), + 'detail' : True, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def performance(request, project_id, instance_id): + project = shortcuts.get_project_or_404(request, project_id) + instance = project.get_instance(instance_id) + + if not instance: + raise http.Http404() + + return render_to_response('django_nova/instances/performance.html', { + 'region': project.region, + 'project': project, + 'instance': instance, + 'update_form': nova_forms.UpdateInstanceForm(instance), + }, context_instance = template.RequestContext(request)) + + +# TODO(devcamcar): Wrap this in an @ajax decorator. +def refresh(request, project_id): + # TODO(devcamcar): This logic belongs in decorator. + if not request.user.is_authenticated(): + return http.HttpResponseForbidden() + + project = shortcuts.get_project_or_404(request, project_id) + instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name) + + return render_to_response('django_nova/instances/_instances_list.html', { + 'project': project, + 'instances': instances, + }, context_instance = template.RequestContext(request)) + + +@handle_nova_error +def refresh_detail(request, project_id, instance_id): + # TODO(devcamcar): This logic belongs in decorator. + if not request.user.is_authenticated(): + return http.HttpResponseForbidden() + + project = shortcuts.get_project_or_404(request, project_id) + instance = project.get_instance(instance_id) + instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name) + + return render_to_response('django_nova/instances/_instances_list.html', { + 'project': project, + 'selected_instance': instance, + 'instances': instances, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def terminate(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + instance_id = request.POST['instance_id'] + + try: + project.terminate_instance(instance_id) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to terminate %s: %s' % + (instance_id, e.message,)) + else: + messages.success(request, + 'Instance %s has been terminated.' % instance_id) + + return redirect('nova_instances', project_id) + + +@login_required +@handle_nova_error +def console(request, project_id, instance_id): + project = shortcuts.get_project_or_404(request, project_id) + conn = project.get_nova_connection() + console = conn.get_console_output(instance_id) + response = http.HttpResponse(mimetype='text/plain') + response.write(console.output) + response.flush() + + return response + + +@login_required +@handle_nova_error +def graph(request, project_id, instance_id, graph_name): + project = shortcuts.get_project_or_404(request, project_id) + graph = project.get_instance_graph(instance_id, graph_name) + + if graph is None: + raise http.Http404() + + response = http.HttpResponse(mimetype='image/png') + response.write(graph) + + return response + + +@login_required +@handle_nova_error +def update(request, project_id, instance_id): + project = shortcuts.get_project_or_404(request, project_id) + instance = project.get_instance(instance_id) + + if not instance: + raise http.Http404() + + if request.method == 'POST': + form = nova_forms.UpdateInstanceForm(instance, request.POST) + if form.is_valid(): + try: + project.update_instance(instance_id, form.cleaned_data) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to update instance %s: %s' % + (instance_id, e.message,)) + else: + messages.success(request, + 'Instance %s has been updated.' % instance_id) + return redirect('nova_instances', project_id) + else: + return render_to_response('django_nova/instances/edit.html', { + 'region': project.region, + 'project': project, + 'instance': instance, + 'update_form': form, + }, context_instance = template.RequestContext(request)) + + else: + return render_to_response('django_nova/instances/edit.html', { + 'region': project.region, + 'project': project, + 'instance': instance, + 'update_form': nova_forms.UpdateInstanceForm(instance), + }, context_instance = template.RequestContext(request)) + diff --git a/django-nova/src/django_nova/views/keypairs.py b/django-nova/src/django_nova/views/keypairs.py new file mode 100644 index 000000000..90db0d2e8 --- /dev/null +++ b/django-nova/src/django_nova/views/keypairs.py @@ -0,0 +1,122 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova keypairs. +""" + +from django import http +from django import template +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render_to_response +from django_nova import exceptions +from django_nova import forms +from django_nova import shortcuts +from django_nova.exceptions import handle_nova_error + + +@login_required +@handle_nova_error +def index(request, project_id, download_key=None): + project = shortcuts.get_project_or_404(request, project_id) + keypairs = project.get_key_pairs() + + return render_to_response('django_nova/keypairs/index.html', { + 'create_form': forms.CreateKeyPairForm(project), + 'region': project.region, + 'project': project, + 'keypairs': keypairs, + 'download_key': download_key + }, context_instance = template.RequestContext(request)) + +@login_required +@handle_nova_error +def add(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + form = forms.CreateKeyPairForm(project, request.POST) + + if form.is_valid(): + try: + keypair = project.create_key_pair(form.cleaned_data['name']) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to create key: %s' % e.message) + else: + if request.POST['js'] == '1': + request.session['key.%s' % keypair.name] = keypair.material + return index(request, + project_id, + download_key=keypair.name) + else: + response = http.HttpResponse(mimetype='application/binary') + response['Content-Disposition'] = \ + 'attachment; filename=%s.pem' % \ + form.cleaned_data['name'] + response.write(keypair.material) + return response + else: + keypairs = project.get_key_pairs() + + return render_to_response('django_nova/keypairs/index.html', { + 'create_form': form, + 'region': project.region, + 'project': project, + 'keypairs': keypairs, + }, context_instance = template.RequestContext(request)) + + return redirect('nova_keypairs', project_id) + +@login_required +@handle_nova_error +def delete(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + key_name = request.POST['key_name'] + + try: + project.delete_key_pair(key_name) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to delete key: %s' % e.message) + else: + messages.success(request, + 'Key %s has been successfully deleted.' % \ + key_name) + + return redirect('nova_keypairs', project_id) + +@login_required +@handle_nova_error +def download(request, project_id, key_name): + # Ensure the project exists. + shortcuts.get_project_or_404(request, project_id) + + try: + material = request.session.pop('key.%s' % key_name) + except KeyError: + return redirect('nova_keypairs', project_id) + + response = http.HttpResponse(mimetype='application/binary') + response['Content-Disposition'] = 'attachment; filename=%s.pem' % key_name + response.write(material) + + return response diff --git a/django-nova/src/django_nova/views/projects.py b/django-nova/src/django_nova/views/projects.py new file mode 100644 index 000000000..b2a217c71 --- /dev/null +++ b/django-nova/src/django_nova/views/projects.py @@ -0,0 +1,107 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova projects. +""" + +from django import http +from django import template +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render_to_response +from django_nova import forms as nova_forms +from django_nova.connection import get_nova_admin_connection +from django_nova.exceptions import handle_nova_error +from django_nova.shortcuts import get_project_or_404 + + +@login_required +@handle_nova_error +def detail(request, project_id): + project = get_project_or_404(request, project_id) + + return render_to_response('django_nova/projects/index.html', { + 'project': project, + 'instance_count': project.get_instance_count(), + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def manage(request, project_id): + project = get_project_or_404(request, project_id) + + if project.projectManagerId != request.user.username: + return redirect('login') + + nova = get_nova_admin_connection() + members = nova.get_project_members(project_id) + + for member in members: + project_role = [str(role.role) for role in nova.get_user_roles(member.memberId, project_id)] + global_role = [str(role.role) for role in nova.get_user_roles(member.memberId, project=False)] + + member.project_roles = ", ".join(project_role) + member.global_roles = ", ".join(global_role) + + + return render_to_response('django_nova/projects/manage.html', { + 'project': project, + 'members': members, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def edit_user(request, project_id, username): + nova = get_nova_admin_connection() + project = get_project_or_404(request, project_id) + user = nova.get_user(username) + + if project.projectManagerId != request.user.username: + return redirect('login') + + if request.method == 'POST': + form = nova_forms.ProjectUserForm(project, user, request.POST) + if form.is_valid(): + form.save() + + return redirect('nova_project_manage', project_id) + else: + form = nova_forms.ProjectUserForm(project, user) + + return render_to_response('django_nova/projects/edit_user.html', { + 'form' : form, + 'project': project, + 'user' : user, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def download_credentials(request, project_id): + project = get_project_or_404(request, project_id) + + response = http.HttpResponse(mimetype='application/zip') + response['Content-Disposition'] = \ + 'attachment; filename=%s-%s-%s-x509.zip' % \ + (settings.SITE_NAME, project.projectname, request.user) + response.write(project.get_zip()) + + return response diff --git a/django-nova/src/django_nova/views/regions.py b/django-nova/src/django_nova/views/regions.py new file mode 100644 index 000000000..189142760 --- /dev/null +++ b/django-nova/src/django_nova/views/regions.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova regions. +""" + +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect +from django_nova.shortcuts import set_current_region + + +@login_required +def change(request): + region = request.POST['region'] + redirect_url = request.POST['redirect_url'] + set_current_region(request, region) + messages.success(request, 'You are now using the region "%s".' % region) + + return redirect(redirect_url) diff --git a/django-nova/src/django_nova/views/securitygroups.py b/django-nova/src/django_nova/views/securitygroups.py new file mode 100644 index 000000000..6f85b4af9 --- /dev/null +++ b/django-nova/src/django_nova/views/securitygroups.py @@ -0,0 +1,180 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova security groups. +""" + +from django import http +from django import template +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render_to_response +from django_nova import exceptions +from django_nova import forms +from django_nova.exceptions import handle_nova_error +from django_nova.shortcuts import get_project_or_404 + + +@login_required +@handle_nova_error +def index(request, project_id): + project = get_project_or_404(request, project_id) + securitygroups = project.get_security_groups() + + return render_to_response('django_nova/securitygroups/index.html', { + 'create_form': forms.CreateSecurityGroupForm(project), + 'project': project, + 'securitygroups': securitygroups, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def detail(request, project_id, group_name): + project = get_project_or_404(request, project_id) + securitygroup = project.get_security_group(group_name) + + if not securitygroup: + raise http.Http404 + + return render_to_response('django_nova/securitygroups/detail.html', { + 'authorize_form': forms.AuthorizeSecurityGroupRuleForm(), + 'project': project, + 'securitygroup': securitygroup, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def add(request, project_id): + project = get_project_or_404(request, project_id) + + if request.method == 'POST': + form = forms.CreateSecurityGroupForm(project, request.POST) + if form.is_valid(): + try: + project.create_security_group( + form.cleaned_data['name'], + form.cleaned_data['description']) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to create security group: %s' % e.message) + else: + messages.success( + request, + 'Security Group %s has been succesfully created.' % \ + form.cleaned_data['name']) + else: + securitygroups = project.get_security_groups() + + return render_to_response('django_nova/securitygroups/index.html', { + 'create_form': form, + 'project': project, + 'securitygroups': securitygroups, + }, context_instance = template.RequestContext(request)) + + return redirect('nova_securitygroups', project_id) + + +@login_required +@handle_nova_error +def authorize(request, project_id, group_name): + project = get_project_or_404(request, project_id) + + if request.method == 'POST': + form = forms.AuthorizeSecurityGroupRuleForm(request.POST) + if form.is_valid(): + try: + project.authorize_security_group( + group_name = group_name, + ip_protocol = form.cleaned_data['protocol'], + from_port = form.cleaned_data['from_port'], + to_port = form.cleaned_data['to_port']) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to authorize: %s' % e.message) + else: + messages.success( + request, + 'Security Group %s: Access to %s ports %d - %d' + ' has been authorized.' % + (group_name, + form.cleaned_data['protocol'], + form.cleaned_data['from_port'], + form.cleaned_data['to_port'])) + else: + securitygroup = project.get_security_group(group_name) + + if not securitygroup: + raise http.Http404 + + return render_to_response('django_nova/securitygroups/detail.html', { + 'authorize_form': form, + 'project': project, + 'securitygroup': securitygroup, + }, context_instance = template.RequestContext(request)) + + return redirect('nova_securitygroups_detail', project_id, group_name) + + +@login_required +@handle_nova_error +def revoke(request, project_id, group_name): + project = get_project_or_404(request, project_id) + + if request.method == 'POST': + try: + project.revoke_security_group( + group_name = group_name, + ip_protocol = request.POST['protocol'], + from_port = request.POST['from_port'], + to_port = request.POST['to_port']) + except exceptions.NovaApiError, e: + messages.error(request, 'Unable to revoke: %s' % e.message) + else: + messages.success( + request, + 'Security Group %s: Access to %s ports %s - %s ' + 'has been revoked.' % + (group_name, + request.POST['protocol'], + request.POST['from_port'], + request.POST['to_port'])) + + return redirect('nova_securitygroups_detail', project_id, group_name) + + +@login_required +@handle_nova_error +def delete(request, project_id, group_name): + project = get_project_or_404(request, project_id) + + if request.method == 'POST': + try: + project.delete_security_group(name=group_name) + except exceptions.NovaApiError, e: + messages.error( + request, + 'Unable to delete security group: %s' % e.message) + else: + messages.success(request, + 'Security Group %s was successfully deleted.' % + group_name) + + return redirect('nova_securitygroups', project_id) diff --git a/django-nova/src/django_nova/views/volumes.py b/django-nova/src/django_nova/views/volumes.py new file mode 100644 index 000000000..9d623ed81 --- /dev/null +++ b/django-nova/src/django_nova/views/volumes.py @@ -0,0 +1,151 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing Nova volumes. +""" + +from django import template +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render_to_response +from django_nova import exceptions +from django_nova import forms +from django_nova import shortcuts +from django_nova.exceptions import handle_nova_error + + +@login_required +@handle_nova_error +def index(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + volumes = project.get_volumes() + + return render_to_response('django_nova/volumes/index.html', { + 'create_form': forms.CreateVolumeForm(), + 'attach_form': forms.AttachVolumeForm(project), + 'region': project.region, + 'project': project, + 'volumes': volumes, + }, context_instance = template.RequestContext(request)) + + +@login_required +@handle_nova_error +def add(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + form = forms.CreateVolumeForm(request.POST) + if form.is_valid(): + try: + volume = project.create_volume(form.cleaned_data['size'], + form.cleaned_data['nickname'], + form.cleaned_data['description']) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to create volume: %s' % e.message) + else: + messages.success(request, + 'Volume %s %s has been successfully created.' % + (volume.id, volume.displayName)) + else: + volumes = project.get_volumes() + + return render_to_response('django_nova/volumes/index.html', { + 'create_form': form, + 'attach_form': forms.AttachVolumeForm(project), + 'region': project.region, + 'project': project, + 'volumes': volumes, + }, context_instance = template.RequestContext(request)) + + return redirect('nova_volumes', project_id) + + +@login_required +@handle_nova_error +def delete(request, project_id, volume_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + try: + project.delete_volume(volume_id) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to delete volume: %s' % e.message) + else: + messages.success(request, + 'Volume %s has been successfully deleted.' + % volume_id) + + return redirect('nova_volumes', project_id) + + +@login_required +@handle_nova_error +def attach(request, project_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + form = forms.AttachVolumeForm(project, request.POST) + if form.is_valid(): + try: + project.attach_volume( + form.cleaned_data['volume'], + form.cleaned_data['instance'], + form.cleaned_data['device'] + ) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to attach volume: %s' % e.message) + else: + messages.success(request, + 'Volume %s has been successfully attached.' % + form.cleaned_data['volume']) + else: + volumes = project.get_volumes() + + return render_to_response('django_nova/volumes/index.html', { + 'create_form': forms.CreateVolumeForm(), + 'attach_form': form, + 'region': project.region, + 'project': project, + 'volumes': volumes, + }, context_instance = template.RequestContext(request)) + + return redirect('nova_volumes', project_id) + + +@login_required +@handle_nova_error +def detach(request, project_id, volume_id): + project = shortcuts.get_project_or_404(request, project_id) + + if request.method == 'POST': + try: + project.detach_volume(volume_id) + except exceptions.NovaApiError, e: + messages.error(request, + 'Unable to detach volume: %s' % e.message) + else: + messages.success(request, + 'Volume %s has been successfully detached.' % + volume_id) + + return redirect('nova_volumes', project_id) diff --git a/openstack-dashboard/README b/openstack-dashboard/README new file mode 100644 index 000000000..3856aca71 --- /dev/null +++ b/openstack-dashboard/README @@ -0,0 +1,49 @@ +OpenStack Dashboard +------------------- + +The OpenStack Dashboard is a reference implementation of a Django site that +uses the Django-Nova project to provide web based interactions with the +OpenStack Nova cloud controller. + +For more information about the Django-Nova project, please visit: + + http://launchpad.net/django-nova + + +Getting Started +--------------- + +The first step is to obtain a local copy of the django-nova project: + + $ mkdir django-nova + $ cd django-nova + $ bzr init-repo . + $ bzr branch lp:django-nova/trunk + + +Next we will create the virtualenv for local development. A tool is included to +create one for you: + + $ python tools/install_venv.py + + +Now that the virtualenv is created, you need to configure your local +environment. To do this, create a local_settings.py file in the local/ +directory. There is a local_settings.py.example file there that may be used +as a template. + +Finally, issue the django syncdb command: + + $ tools/with_venv.sh dashboard/manage.py syncdb + +If after you have specified the admin user the script appears to hang, it +probably means the installation of Nova being referred to in local_settings.py +is unavailable. + + +If all is well you should now able to run the server locally: + + $ tools/with_venv.sh dashboard/manage.py runserver + + + diff --git a/openstack-dashboard/dashboard/__init__.py b/openstack-dashboard/dashboard/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dashboard/manage.py b/openstack-dashboard/dashboard/manage.py similarity index 100% rename from dashboard/manage.py rename to openstack-dashboard/dashboard/manage.py diff --git a/dashboard/settings.py b/openstack-dashboard/dashboard/settings.py similarity index 100% rename from dashboard/settings.py rename to openstack-dashboard/dashboard/settings.py diff --git a/dashboard/templates/403.html b/openstack-dashboard/dashboard/templates/403.html similarity index 100% rename from dashboard/templates/403.html rename to openstack-dashboard/dashboard/templates/403.html diff --git a/dashboard/templates/404.html b/openstack-dashboard/dashboard/templates/404.html similarity index 100% rename from dashboard/templates/404.html rename to openstack-dashboard/dashboard/templates/404.html diff --git a/dashboard/templates/500.html b/openstack-dashboard/dashboard/templates/500.html similarity index 100% rename from dashboard/templates/500.html rename to openstack-dashboard/dashboard/templates/500.html diff --git a/dashboard/templates/admin/index.html b/openstack-dashboard/dashboard/templates/admin/index.html similarity index 100% rename from dashboard/templates/admin/index.html rename to openstack-dashboard/dashboard/templates/admin/index.html diff --git a/dashboard/templates/base-root.html b/openstack-dashboard/dashboard/templates/base-root.html similarity index 100% rename from dashboard/templates/base-root.html rename to openstack-dashboard/dashboard/templates/base-root.html diff --git a/dashboard/templates/base-sidebar.html b/openstack-dashboard/dashboard/templates/base-sidebar.html similarity index 100% rename from dashboard/templates/base-sidebar.html rename to openstack-dashboard/dashboard/templates/base-sidebar.html diff --git a/dashboard/templates/base.html b/openstack-dashboard/dashboard/templates/base.html similarity index 100% rename from dashboard/templates/base.html rename to openstack-dashboard/dashboard/templates/base.html diff --git a/dashboard/templates/index.html b/openstack-dashboard/dashboard/templates/index.html similarity index 92% rename from dashboard/templates/index.html rename to openstack-dashboard/dashboard/templates/index.html index ba99a5d13..14820687a 100644 --- a/dashboard/templates/index.html +++ b/openstack-dashboard/dashboard/templates/index.html @@ -36,8 +36,8 @@

    OpenStack Resources

    @@ -69,8 +69,8 @@

    OpenStack Resources

    {% endif %} diff --git a/dashboard/templates/permission_denied.html b/openstack-dashboard/dashboard/templates/permission_denied.html similarity index 100% rename from dashboard/templates/permission_denied.html rename to openstack-dashboard/dashboard/templates/permission_denied.html diff --git a/dashboard/templates/registration/activate.html b/openstack-dashboard/dashboard/templates/registration/activate.html similarity index 100% rename from dashboard/templates/registration/activate.html rename to openstack-dashboard/dashboard/templates/registration/activate.html diff --git a/dashboard/templates/registration/activation_email.txt b/openstack-dashboard/dashboard/templates/registration/activation_email.txt similarity index 100% rename from dashboard/templates/registration/activation_email.txt rename to openstack-dashboard/dashboard/templates/registration/activation_email.txt diff --git a/dashboard/templates/registration/activation_email_subject.txt b/openstack-dashboard/dashboard/templates/registration/activation_email_subject.txt similarity index 100% rename from dashboard/templates/registration/activation_email_subject.txt rename to openstack-dashboard/dashboard/templates/registration/activation_email_subject.txt diff --git a/dashboard/templates/registration/login.html b/openstack-dashboard/dashboard/templates/registration/login.html similarity index 100% rename from dashboard/templates/registration/login.html rename to openstack-dashboard/dashboard/templates/registration/login.html diff --git a/dashboard/templates/registration/logout.html b/openstack-dashboard/dashboard/templates/registration/logout.html similarity index 100% rename from dashboard/templates/registration/logout.html rename to openstack-dashboard/dashboard/templates/registration/logout.html diff --git a/dashboard/templates/registration/password_change_done.html b/openstack-dashboard/dashboard/templates/registration/password_change_done.html similarity index 100% rename from dashboard/templates/registration/password_change_done.html rename to openstack-dashboard/dashboard/templates/registration/password_change_done.html diff --git a/dashboard/templates/registration/password_change_form.html b/openstack-dashboard/dashboard/templates/registration/password_change_form.html similarity index 100% rename from dashboard/templates/registration/password_change_form.html rename to openstack-dashboard/dashboard/templates/registration/password_change_form.html diff --git a/dashboard/templates/registration/password_reset_complete.html b/openstack-dashboard/dashboard/templates/registration/password_reset_complete.html similarity index 100% rename from dashboard/templates/registration/password_reset_complete.html rename to openstack-dashboard/dashboard/templates/registration/password_reset_complete.html diff --git a/dashboard/templates/registration/password_reset_confirm.html b/openstack-dashboard/dashboard/templates/registration/password_reset_confirm.html similarity index 100% rename from dashboard/templates/registration/password_reset_confirm.html rename to openstack-dashboard/dashboard/templates/registration/password_reset_confirm.html diff --git a/dashboard/templates/registration/password_reset_done.html b/openstack-dashboard/dashboard/templates/registration/password_reset_done.html similarity index 100% rename from dashboard/templates/registration/password_reset_done.html rename to openstack-dashboard/dashboard/templates/registration/password_reset_done.html diff --git a/dashboard/templates/registration/password_reset_email.html b/openstack-dashboard/dashboard/templates/registration/password_reset_email.html similarity index 100% rename from dashboard/templates/registration/password_reset_email.html rename to openstack-dashboard/dashboard/templates/registration/password_reset_email.html diff --git a/dashboard/templates/registration/password_reset_form.html b/openstack-dashboard/dashboard/templates/registration/password_reset_form.html similarity index 100% rename from dashboard/templates/registration/password_reset_form.html rename to openstack-dashboard/dashboard/templates/registration/password_reset_form.html diff --git a/dashboard/templates/registration/registration_complete.html b/openstack-dashboard/dashboard/templates/registration/registration_complete.html similarity index 100% rename from dashboard/templates/registration/registration_complete.html rename to openstack-dashboard/dashboard/templates/registration/registration_complete.html diff --git a/dashboard/templates/registration/registration_form.html b/openstack-dashboard/dashboard/templates/registration/registration_form.html similarity index 100% rename from dashboard/templates/registration/registration_form.html rename to openstack-dashboard/dashboard/templates/registration/registration_form.html diff --git a/dashboard/templates/unavailable.html b/openstack-dashboard/dashboard/templates/unavailable.html similarity index 100% rename from dashboard/templates/unavailable.html rename to openstack-dashboard/dashboard/templates/unavailable.html diff --git a/dashboard/urls.py b/openstack-dashboard/dashboard/urls.py similarity index 100% rename from dashboard/urls.py rename to openstack-dashboard/dashboard/urls.py diff --git a/dashboard/views.py b/openstack-dashboard/dashboard/views.py similarity index 100% rename from dashboard/views.py rename to openstack-dashboard/dashboard/views.py diff --git a/dashboard/wsgi/django.wsgi b/openstack-dashboard/dashboard/wsgi/django.wsgi similarity index 100% rename from dashboard/wsgi/django.wsgi rename to openstack-dashboard/dashboard/wsgi/django.wsgi diff --git a/openstack-dashboard/local/__init__.py b/openstack-dashboard/local/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/local/local_settings.py.example b/openstack-dashboard/local/local_settings.py.example similarity index 100% rename from local/local_settings.py.example rename to openstack-dashboard/local/local_settings.py.example diff --git a/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png diff --git a/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png diff --git a/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png diff --git a/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png diff --git a/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png diff --git a/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png similarity index 100% rename from media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png rename to openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png diff --git a/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css b/openstack-dashboard/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css similarity index 100% rename from media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css rename to openstack-dashboard/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css diff --git a/media/dashboard/css/django-admin-widgets.css b/openstack-dashboard/media/dashboard/css/django-admin-widgets.css similarity index 100% rename from media/dashboard/css/django-admin-widgets.css rename to openstack-dashboard/media/dashboard/css/django-admin-widgets.css diff --git a/media/dashboard/css/ie7.css b/openstack-dashboard/media/dashboard/css/ie7.css similarity index 100% rename from media/dashboard/css/ie7.css rename to openstack-dashboard/media/dashboard/css/ie7.css diff --git a/media/dashboard/css/openstack.css b/openstack-dashboard/media/dashboard/css/openstack.css similarity index 100% rename from media/dashboard/css/openstack.css rename to openstack-dashboard/media/dashboard/css/openstack.css diff --git a/media/dashboard/css/reset.css b/openstack-dashboard/media/dashboard/css/reset.css similarity index 100% rename from media/dashboard/css/reset.css rename to openstack-dashboard/media/dashboard/css/reset.css diff --git a/media/dashboard/img/body_bg.gif b/openstack-dashboard/media/dashboard/img/body_bg.gif similarity index 100% rename from media/dashboard/img/body_bg.gif rename to openstack-dashboard/media/dashboard/img/body_bg.gif diff --git a/media/dashboard/img/body_bg.png b/openstack-dashboard/media/dashboard/img/body_bg.png similarity index 100% rename from media/dashboard/img/body_bg.png rename to openstack-dashboard/media/dashboard/img/body_bg.png diff --git a/media/dashboard/img/book_icon.png b/openstack-dashboard/media/dashboard/img/book_icon.png similarity index 100% rename from media/dashboard/img/book_icon.png rename to openstack-dashboard/media/dashboard/img/book_icon.png diff --git a/media/dashboard/img/bread_crumb.gif b/openstack-dashboard/media/dashboard/img/bread_crumb.gif similarity index 100% rename from media/dashboard/img/bread_crumb.gif rename to openstack-dashboard/media/dashboard/img/bread_crumb.gif diff --git a/media/dashboard/img/btn_bg.png b/openstack-dashboard/media/dashboard/img/btn_bg.png similarity index 100% rename from media/dashboard/img/btn_bg.png rename to openstack-dashboard/media/dashboard/img/btn_bg.png diff --git a/media/dashboard/img/chat_icon.png b/openstack-dashboard/media/dashboard/img/chat_icon.png similarity index 100% rename from media/dashboard/img/chat_icon.png rename to openstack-dashboard/media/dashboard/img/chat_icon.png diff --git a/media/dashboard/img/content_bg.gif b/openstack-dashboard/media/dashboard/img/content_bg.gif similarity index 100% rename from media/dashboard/img/content_bg.gif rename to openstack-dashboard/media/dashboard/img/content_bg.gif diff --git a/media/dashboard/img/content_shadow.png b/openstack-dashboard/media/dashboard/img/content_shadow.png similarity index 100% rename from media/dashboard/img/content_shadow.png rename to openstack-dashboard/media/dashboard/img/content_shadow.png diff --git a/media/dashboard/img/dashboard_nav_bg.png b/openstack-dashboard/media/dashboard/img/dashboard_nav_bg.png similarity index 100% rename from media/dashboard/img/dashboard_nav_bg.png rename to openstack-dashboard/media/dashboard/img/dashboard_nav_bg.png diff --git a/media/dashboard/img/foot_back.png b/openstack-dashboard/media/dashboard/img/foot_back.png similarity index 100% rename from media/dashboard/img/foot_back.png rename to openstack-dashboard/media/dashboard/img/foot_back.png diff --git a/media/dashboard/img/gears.png b/openstack-dashboard/media/dashboard/img/gears.png similarity index 100% rename from media/dashboard/img/gears.png rename to openstack-dashboard/media/dashboard/img/gears.png diff --git a/media/dashboard/img/header_bg.png b/openstack-dashboard/media/dashboard/img/header_bg.png similarity index 100% rename from media/dashboard/img/header_bg.png rename to openstack-dashboard/media/dashboard/img/header_bg.png diff --git a/media/dashboard/img/home_head_back.png b/openstack-dashboard/media/dashboard/img/home_head_back.png similarity index 100% rename from media/dashboard/img/home_head_back.png rename to openstack-dashboard/media/dashboard/img/home_head_back.png diff --git a/media/dashboard/img/image_detail.png b/openstack-dashboard/media/dashboard/img/image_detail.png similarity index 100% rename from media/dashboard/img/image_detail.png rename to openstack-dashboard/media/dashboard/img/image_detail.png diff --git a/media/dashboard/img/logged_in_box_bg.gif b/openstack-dashboard/media/dashboard/img/logged_in_box_bg.gif similarity index 100% rename from media/dashboard/img/logged_in_box_bg.gif rename to openstack-dashboard/media/dashboard/img/logged_in_box_bg.gif diff --git a/media/dashboard/img/login_bg.png b/openstack-dashboard/media/dashboard/img/login_bg.png similarity index 100% rename from media/dashboard/img/login_bg.png rename to openstack-dashboard/media/dashboard/img/login_bg.png diff --git a/media/dashboard/img/login_btn.png b/openstack-dashboard/media/dashboard/img/login_btn.png similarity index 100% rename from media/dashboard/img/login_btn.png rename to openstack-dashboard/media/dashboard/img/login_btn.png diff --git a/media/dashboard/img/logo.gif b/openstack-dashboard/media/dashboard/img/logo.gif similarity index 100% rename from media/dashboard/img/logo.gif rename to openstack-dashboard/media/dashboard/img/logo.gif diff --git a/media/dashboard/img/nav_arrow.png b/openstack-dashboard/media/dashboard/img/nav_arrow.png similarity index 100% rename from media/dashboard/img/nav_arrow.png rename to openstack-dashboard/media/dashboard/img/nav_arrow.png diff --git a/media/dashboard/img/nav_bg.png b/openstack-dashboard/media/dashboard/img/nav_bg.png similarity index 100% rename from media/dashboard/img/nav_bg.png rename to openstack-dashboard/media/dashboard/img/nav_bg.png diff --git a/media/dashboard/img/nav_highlight.png b/openstack-dashboard/media/dashboard/img/nav_highlight.png similarity index 100% rename from media/dashboard/img/nav_highlight.png rename to openstack-dashboard/media/dashboard/img/nav_highlight.png diff --git a/media/dashboard/img/page_header.png b/openstack-dashboard/media/dashboard/img/page_header.png similarity index 100% rename from media/dashboard/img/page_header.png rename to openstack-dashboard/media/dashboard/img/page_header.png diff --git a/media/dashboard/img/projects_bg.png b/openstack-dashboard/media/dashboard/img/projects_bg.png similarity index 100% rename from media/dashboard/img/projects_bg.png rename to openstack-dashboard/media/dashboard/img/projects_bg.png diff --git a/media/dashboard/img/server_icon.png b/openstack-dashboard/media/dashboard/img/server_icon.png similarity index 100% rename from media/dashboard/img/server_icon.png rename to openstack-dashboard/media/dashboard/img/server_icon.png diff --git a/media/dashboard/img/spinner.gif b/openstack-dashboard/media/dashboard/img/spinner.gif similarity index 100% rename from media/dashboard/img/spinner.gif rename to openstack-dashboard/media/dashboard/img/spinner.gif diff --git a/media/dashboard/img/sub-head-back.png b/openstack-dashboard/media/dashboard/img/sub-head-back.png similarity index 100% rename from media/dashboard/img/sub-head-back.png rename to openstack-dashboard/media/dashboard/img/sub-head-back.png diff --git a/media/dashboard/img/sub_head_back.png b/openstack-dashboard/media/dashboard/img/sub_head_back.png similarity index 100% rename from media/dashboard/img/sub_head_back.png rename to openstack-dashboard/media/dashboard/img/sub_head_back.png diff --git a/media/dashboard/img/table_header_bg.png b/openstack-dashboard/media/dashboard/img/table_header_bg.png similarity index 100% rename from media/dashboard/img/table_header_bg.png rename to openstack-dashboard/media/dashboard/img/table_header_bg.png diff --git a/media/dashboard/img/table_heading_bg.png b/openstack-dashboard/media/dashboard/img/table_heading_bg.png similarity index 100% rename from media/dashboard/img/table_heading_bg.png rename to openstack-dashboard/media/dashboard/img/table_heading_bg.png diff --git a/media/dashboard/img/title-blank-short-foot.png b/openstack-dashboard/media/dashboard/img/title-blank-short-foot.png similarity index 100% rename from media/dashboard/img/title-blank-short-foot.png rename to openstack-dashboard/media/dashboard/img/title-blank-short-foot.png diff --git a/media/dashboard/js/dashboard.js b/openstack-dashboard/media/dashboard/js/dashboard.js similarity index 100% rename from media/dashboard/js/dashboard.js rename to openstack-dashboard/media/dashboard/js/dashboard.js diff --git a/media/dashboard/js/django-admin.multiselect.js b/openstack-dashboard/media/dashboard/js/django-admin.multiselect.js similarity index 100% rename from media/dashboard/js/django-admin.multiselect.js rename to openstack-dashboard/media/dashboard/js/django-admin.multiselect.js diff --git a/media/dashboard/js/jquery-ui.min.js b/openstack-dashboard/media/dashboard/js/jquery-ui.min.js similarity index 100% rename from media/dashboard/js/jquery-ui.min.js rename to openstack-dashboard/media/dashboard/js/jquery-ui.min.js diff --git a/media/dashboard/js/jquery.form.js b/openstack-dashboard/media/dashboard/js/jquery.form.js similarity index 100% rename from media/dashboard/js/jquery.form.js rename to openstack-dashboard/media/dashboard/js/jquery.form.js diff --git a/media/dashboard/js/jquery.min.js b/openstack-dashboard/media/dashboard/js/jquery.min.js similarity index 100% rename from media/dashboard/js/jquery.min.js rename to openstack-dashboard/media/dashboard/js/jquery.min.js diff --git a/run_tests.sh b/openstack-dashboard/run_tests.sh similarity index 100% rename from run_tests.sh rename to openstack-dashboard/run_tests.sh diff --git a/tools/install_venv.py b/openstack-dashboard/tools/install_venv.py similarity index 87% rename from tools/install_venv.py rename to openstack-dashboard/tools/install_venv.py index 987e8b88f..8e90e60c3 100644 --- a/tools/install_venv.py +++ b/openstack-dashboard/tools/install_venv.py @@ -32,6 +32,7 @@ VENV = os.path.join(ROOT, '.dashboard-venv') WITH_VENV = os.path.join(ROOT, 'tools', 'with_venv.sh') PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') + def die(message, *args): print >>sys.stderr, message % args sys.exit(1) @@ -97,21 +98,11 @@ def install_dependencies(venv=VENV): f.write("%s\n" % ROOT) -def install_django_nova(path): +def install_django_nova(): print 'Installing django_nova in development mode...' + path = os.path.join(ROOT, '..', 'django-nova') run_command([WITH_VENV, 'python', 'setup.py', 'develop'], cwd=path) -def print_usage(): - usage = """ - OpenStack Dashboard development uses virtualenv to track and manage Python - dependencies while in development and testing. - - It uses the OpenStack django_nova module. For more information on how to - obtian the django_nova module, please refer to the README file. - - usage: python install_venv.py - """ - print usage def print_summary(): summary = """ @@ -125,15 +116,12 @@ def print_summary(): print summary -def main(argv): - if len(argv) != 2: - print_usage() - sys.exit(1) +def main(): check_dependencies() create_virtualenv() install_dependencies() - install_django_nova(argv[1]) + install_django_nova() print_summary() if __name__ == '__main__': - main(sys.argv) + main() diff --git a/tools/pip-requires b/openstack-dashboard/tools/pip-requires similarity index 100% rename from tools/pip-requires rename to openstack-dashboard/tools/pip-requires diff --git a/tools/with_venv.sh b/openstack-dashboard/tools/with_venv.sh similarity index 100% rename from tools/with_venv.sh rename to openstack-dashboard/tools/with_venv.sh