Commit 23530b0b by Szabolcs Gelencser

Merge usernetworks

parents 132dd4a5 cc5b0edb
......@@ -2,24 +2,51 @@
<project version="4">
<component name="ChangeListManager">
<list default="true" id="1fbec8af-5a7c-40f9-b994-83ac07d1ae1d" name="Default" comment="">
<change beforePath="" afterPath="$PROJECT_DIR$/circle/acl/views.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/circle/settings/static_and_pipeline.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/circle/settings/util.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/dashboard/migrations/0007_profile_network_limit.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index-vxlans.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/migrations/0001_initial.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/migrations/0002_auto_20171110_0041.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/migrations/0003_editorelement.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/migrations/__init__.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/static/network/editor.es6" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/static/network/editor.less" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/editor.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/vxlan-create.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/vxlan-edit.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/vxlan-list.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/vxlan-superuser-create.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/vxlan-superuser-edit.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/network/templates/network/vxlan-superuser-list.html" />
<change beforePath="" afterPath="$PROJECT_DIR$/circle/vm/migrations/0003_auto_20171105_2011.py" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
<change beforePath="$PROJECT_DIR$/circle/acl/__init__.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/management/__init__.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/management/commands/__init__.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/management/commands/update_levels.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/migrations/0001_initial.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/migrations/__init__.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/models.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/tests/__init__.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/tests/models.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/tests/test_acl.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/acl/views.py" afterPath="" />
<change beforePath="$PROJECT_DIR$/circle/bower.json" afterPath="$PROJECT_DIR$/circle/bower.json" />
<change beforePath="$PROJECT_DIR$/circle/circle/db.sqlite3" afterPath="$PROJECT_DIR$/circle/circle/db.sqlite3" />
<change beforePath="$PROJECT_DIR$/circle/circle/settings/base.py" afterPath="$PROJECT_DIR$/circle/circle/settings/base.py" />
<change beforePath="$PROJECT_DIR$/circle/circle/settings/local.py" afterPath="$PROJECT_DIR$/circle/circle/settings/local.py" />
<change beforePath="$PROJECT_DIR$/circle/circle/urls.py" afterPath="$PROJECT_DIR$/circle/circle/urls.py" />
<change beforePath="$PROJECT_DIR$/circle/common/views.py" afterPath="$PROJECT_DIR$/circle/common/views.py" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/forms.py" afterPath="$PROJECT_DIR$/circle/dashboard/forms.py" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/management/commands/init.py" afterPath="$PROJECT_DIR$/circle/dashboard/management/commands/init.py" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/models.py" afterPath="$PROJECT_DIR$/circle/dashboard/models.py" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/views/vm.py" afterPath="$PROJECT_DIR$/circle/dashboard/views/vm.py" />
<change beforePath="$PROJECT_DIR$/circle/firewall/models.py" afterPath="$PROJECT_DIR$/circle/firewall/models.py" />
<change beforePath="$PROJECT_DIR$/circle/vm/models/common.py" afterPath="$PROJECT_DIR$/circle/vm/models/common.py" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/static/dashboard/activity.js" afterPath="$PROJECT_DIR$/circle/dashboard/static/dashboard/activity.js" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/static/dashboard/dashboard.js" afterPath="$PROJECT_DIR$/circle/dashboard/static/dashboard/dashboard.js" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/static/dashboard/dashboard.less" afterPath="$PROJECT_DIR$/circle/dashboard/static/dashboard/dashboard.less" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index.html" afterPath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index.html" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/vm-detail/network.html" afterPath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/vm-detail/network.html" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/vm-list.html" afterPath="$PROJECT_DIR$/circle/dashboard/templates/dashboard/vm-list.html" />
<change beforePath="$PROJECT_DIR$/circle/dashboard/views/index.py" afterPath="$PROJECT_DIR$/circle/dashboard/views/index.py" />
<change beforePath="$PROJECT_DIR$/circle/network/forms.py" afterPath="$PROJECT_DIR$/circle/network/forms.py" />
<change beforePath="$PROJECT_DIR$/circle/network/models.py" afterPath="$PROJECT_DIR$/circle/network/models.py" />
<change beforePath="$PROJECT_DIR$/circle/network/tables.py" afterPath="$PROJECT_DIR$/circle/network/tables.py" />
<change beforePath="$PROJECT_DIR$/circle/network/templates/network/dashboard.html" afterPath="$PROJECT_DIR$/circle/network/templates/network/dashboard.html" />
<change beforePath="$PROJECT_DIR$/circle/network/urls.py" afterPath="$PROJECT_DIR$/circle/network/urls.py" />
<change beforePath="$PROJECT_DIR$/circle/network/views.py" afterPath="$PROJECT_DIR$/circle/network/views.py" />
<change beforePath="$PROJECT_DIR$/circle/vm/models/instance.py" afterPath="$PROJECT_DIR$/circle/vm/models/instance.py" />
<change beforePath="$PROJECT_DIR$/circle/vm/models/network.py" afterPath="$PROJECT_DIR$/circle/vm/models/network.py" />
<change beforePath="$PROJECT_DIR$/circle/vm/operations.py" afterPath="$PROJECT_DIR$/circle/vm/operations.py" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
......@@ -44,66 +71,7 @@
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" />
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file leaf-file-name="common.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/circle/vm/models/common.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-816">
<caret line="29" column="41" lean-forward="true" selection-start-line="29" selection-start-column="41" selection-end-line="29" selection-end-column="41" />
<folding>
<element signature="e#732#788#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="instance.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/circle/vm/models/instance.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="182">
<caret line="212" column="11" lean-forward="false" selection-start-line="212" selection-start-column="11" selection-end-line="212" selection-end-column="11" />
<folding>
<element signature="e#732#788#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="models.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/circle/firewall/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="182">
<caret line="357" column="48" lean-forward="false" selection-start-line="357" selection-start-column="48" selection-end-line="357" selection-end-column="48" />
<folding>
<element signature="e#757#789#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="vm.py" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/circle/dashboard/views/vm.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="476">
<caret line="141" column="39" lean-forward="true" selection-start-line="141" selection-start-column="39" selection-end-line="141" selection-end-column="39" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="models.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/circle/dashboard/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="182">
<caret line="359" column="58" lean-forward="false" selection-start-line="359" selection-start-column="58" selection-end-line="359" selection-end-column="58" />
<folding>
<element signature="e#732#770#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
......@@ -115,11 +83,6 @@
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>instance</find>
<find>Instance</find>
<find>Instance(</find>
<find>get_ob</find>
<find>check_auth</find>
<find>installed</find>
<find>openstack_auth</find>
<find>vm_ops</find>
......@@ -144,7 +107,12 @@
<find>destroyope</find>
<find>ACL_LEVELS</find>
<find>a</find>
<find>get_env_variable</find>
<find>editor</find>
<find>network</find>
<find>acl</find>
<find>check</find>
<find>checked</find>
</findStrings>
<replaceStrings>
<replace>'ACTIVE'</replace>
......@@ -156,7 +124,6 @@
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/circle/static_collected/dashboard/dashboard.less" />
<option value="$PROJECT_DIR$/circle/vm/models/__init__.py" />
<option value="$PROJECT_DIR$/circle/dashboard/admin.py" />
<option value="$PROJECT_DIR$/circle/request/models.py" />
......@@ -178,11 +145,9 @@
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index-vm.html" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/base.html" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/base.html" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index.html" />
<option value="$PROJECT_DIR$/circle/dashboard/forms.py" />
<option value="$PROJECT_DIR$/circle/vm/managers/os_instance_manager.py" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/vm-detail/home.html" />
<option value="$PROJECT_DIR$/circle/circle/urls.py" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/vm-detail/access.html" />
<option value="$PROJECT_DIR$/circle/dashboard/views/__init__.py" />
<option value="$PROJECT_DIR$/circle/dashboard/views/index.py" />
......@@ -197,15 +162,19 @@
<option value="$PROJECT_DIR$/circle/circle/os_policies/keystone_policy.json" />
<option value="$PROJECT_DIR$/circle/openstack_auth/__init__.py" />
<option value="$PROJECT_DIR$/circle/circle/os_policies/nova_policy.json" />
<option value="$PROJECT_DIR$/circle/vm/operations.py" />
<option value="$PROJECT_DIR$/circle/dashboard/views/util.py" />
<option value="$PROJECT_DIR$/circle/common/operations.py" />
<option value="$PROJECT_DIR$/circle/circle/settings/base.py" />
<option value="$PROJECT_DIR$/circle/vm/models/common.py" />
<option value="$PROJECT_DIR$/circle/vm/models/instance.py" />
<option value="$PROJECT_DIR$/circle/firewall/models.py" />
<option value="$PROJECT_DIR$/circle/dashboard/models.py" />
<option value="$PROJECT_DIR$/circle/dashboard/views/vm.py" />
<option value="$PROJECT_DIR$/circle/circle/settings/base.py" />
<option value="$PROJECT_DIR$/circle/vm/operations.py" />
<option value="$PROJECT_DIR$/circle/network/models.py" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index.html" />
<option value="$PROJECT_DIR$/circle/circle/urls.py" />
<option value="$PROJECT_DIR$/circle/network/views.py" />
</list>
</option>
</component>
......@@ -275,40 +244,13 @@
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="os_policies" type="462c0819:PsiDirectoryNode" />
<item name="acl" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="settings" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="openstack_auth" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="vm" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="vm" type="462c0819:PsiDirectoryNode" />
<item name="models" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" />
......@@ -332,7 +274,7 @@
<property name="settings.editor.selected.configurable" value="preferences.keymap" />
<property name="NewWatcherDialog.advanced.open" value="true" />
<property name="DefaultHtmlFileTemplate" value="HTML File" />
<property name="SearchEverywhereHistoryKey" value="sleep&#9;null&#9;null&#10;Deploy&#9;null&#9;null&#10;Instance&#9;null&#9;null&#10;Vmdeta&#9;null&#9;null&#10;list_from&#9;null&#9;null&#10;aclupda&#9;null&#9;null&#10;base.htm&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/base.html&#10;method&#9;ACTION&#9;GoToMenuEx&#10;index.html&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/dashboard/index.html&#10;base.ht&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/base.html&#10;base.html&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/dashboard/base.html&#10;index.py&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/views/index.py&#10;index.htm&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/dashboard/index.html&#10;server&#9;null&#9;null&#10;Disk&#9;null&#9;null" />
<property name="SearchEverywhereHistoryKey" value="index.html&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/dashboard/index.html&#10;sleep&#9;null&#9;null&#10;Deploy&#9;null&#9;null&#10;Instance&#9;null&#9;null&#10;Vmdeta&#9;null&#9;null&#10;list_from&#9;null&#9;null&#10;aclupda&#9;null&#9;null&#10;base.htm&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/base.html&#10;method&#9;ACTION&#9;GoToMenuEx&#10;base.ht&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/base.html&#10;base.html&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/dashboard/base.html&#10;index.py&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/views/index.py&#10;index.htm&#9;FILE&#9;file:///home/h3yduck/cloud/circle/dashboard/templates/dashboard/index.html&#10;server&#9;null&#9;null&#10;Disk&#9;null&#9;null" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
......@@ -394,21 +336,20 @@
</component>
<component name="ToolWindowManager">
<frame x="-2" y="-1" width="1924" height="1063" extended-state="0" />
<editor active="true" />
<layout>
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.327818" sideWeight="0.5021299" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32875264" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.43023255" sideWeight="0.43610224" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32875264" sideWeight="0.4978701" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.43023255" sideWeight="0.43610224" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.15867944" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.43023255" sideWeight="0.43823215" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.15867944" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="Docker" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="SciView" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.43023255" sideWeight="0.5638978" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.43023255" sideWeight="0.5617678" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
......@@ -571,50 +512,6 @@
</expressions>
</component>
<component name="editorHistoryManager">
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/core/servers/basehttp.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="181">
<caret line="154" column="0" lean-forward="false" selection-start-line="154" selection-start-column="0" selection-end-line="154" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file:///usr/lib/python2.7/wsgiref/handlers.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="181">
<caret line="84" column="0" lean-forward="false" selection-start-line="84" selection-start-column="0" selection-end-line="84" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/contrib/staticfiles/handlers.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="408">
<caret line="62" column="0" lean-forward="false" selection-start-line="62" selection-start-column="0" selection-end-line="62" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/core/handlers/wsgi.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="181">
<caret line="156" column="0" lean-forward="false" selection-start-line="156" selection-start-column="0" selection-end-line="156" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/views/generic/base.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="275">
<caret line="67" column="0" lean-forward="false" selection-start-line="67" selection-start-column="0" selection-end-line="67" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/views/generic/detail.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="198">
<caret line="115" column="0" lean-forward="false" selection-start-line="115" selection-start-column="0" selection-end-line="115" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/templates/dashboard/operate.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
......@@ -693,6 +590,7 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="190">
<caret line="40" column="0" lean-forward="false" selection-start-line="40" selection-start-column="0" selection-end-line="40" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
......@@ -746,13 +644,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/acl/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/common/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="185">
......@@ -777,13 +668,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="590">
<caret line="226" column="22" lean-forward="true" selection-start-line="226" selection-start-column="22" selection-end-line="226" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file:///usr/lib/python2.7/importlib/__init__.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="377">
......@@ -861,16 +745,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/vm/operations.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="130" column="0" lean-forward="false" selection-start-line="130" selection-start-column="0" selection-end-line="130" selection-end-column="0" />
<folding>
<element signature="e#732#788#0" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/views/util.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="4437">
......@@ -903,14 +777,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/circle/settings/base.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="318">
<caret line="370" column="0" lean-forward="false" selection-start-line="370" selection-start-column="0" selection-end-line="370" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/db/models/base.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="306">
......@@ -922,9 +788,9 @@
<entry file="file://$PROJECT_DIR$/circle/vm/models/common.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-816">
<caret line="29" column="41" lean-forward="true" selection-start-line="29" selection-start-column="41" selection-end-line="29" selection-end-column="41" />
<caret line="29" column="41" lean-forward="false" selection-start-line="29" selection-start-column="41" selection-end-line="29" selection-end-column="41" />
<folding>
<element signature="e#732#788#0" expanded="true" />
<element signature="e#732#788#0" expanded="false" />
</folding>
</state>
</provider>
......@@ -934,7 +800,7 @@
<state relative-caret-position="182">
<caret line="212" column="11" lean-forward="false" selection-start-line="212" selection-start-column="11" selection-end-line="212" selection-end-column="11" />
<folding>
<element signature="e#732#788#0" expanded="true" />
<element signature="e#732#788#0" expanded="false" />
</folding>
</state>
</provider>
......@@ -944,25 +810,115 @@
<state relative-caret-position="182">
<caret line="357" column="48" lean-forward="false" selection-start-line="357" selection-start-column="48" selection-end-line="357" selection-end-column="48" />
<folding>
<element signature="e#757#789#0" expanded="true" />
<element signature="e#757#789#0" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/views/vm.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="476">
<caret line="141" column="39" lean-forward="false" selection-start-line="141" selection-start-column="39" selection-end-line="141" selection-end-column="39" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="182">
<caret line="359" column="58" lean-forward="false" selection-start-line="359" selection-start-column="58" selection-end-line="359" selection-end-column="58" />
<folding>
<element signature="e#732#770#0" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/circle/settings/base.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="313">
<caret line="541" column="35" lean-forward="true" selection-start-line="541" selection-start-column="35" selection-end-line="541" selection-end-column="35" />
<folding>
<element signature="e#782#791#0" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/network/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="170">
<caret line="35" column="32" lean-forward="false" selection-start-line="35" selection-start-column="32" selection-end-line="35" selection-end-column="32" />
<folding>
<element signature="e#732#760#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/vm/operations.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="299">
<caret line="198" column="25" lean-forward="true" selection-start-line="198" selection-start-column="25" selection-end-line="198" selection-end-column="25" />
<folding>
<element signature="e#732#788#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/templates/dashboard/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="471">
<caret line="39" column="41" lean-forward="true" selection-start-line="39" selection-start-column="41" selection-end-line="39" selection-end-column="41" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-931">
<caret line="55" column="61" lean-forward="false" selection-start-line="55" selection-start-column="61" selection-end-line="55" selection-end-column="61" />
<folding>
<element signature="e#732#770#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/dashboard/views/vm.py">
<entry file="file://$PROJECT_DIR$/circle/circle/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="476">
<caret line="141" column="39" lean-forward="true" selection-start-line="141" selection-start-column="39" selection-end-line="141" selection-end-column="39" />
<state relative-caret-position="85">
<caret line="33" column="36" lean-forward="false" selection-start-line="33" selection-start-column="36" selection-end-line="33" selection-end-column="36" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/acl/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-89">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/network/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="182">
<caret line="965" column="84" lean-forward="false" selection-start-line="965" selection-start-column="84" selection-end-line="965" selection-end-column="84" />
<folding>
<element signature="e#732#746#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.virtualenvs/cloud/local/lib/python2.7/site-packages/django/conf/urls/__init__.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="318">
<caret line="57" column="45" lean-forward="true" selection-start-line="57" selection-start-column="45" selection-end-line="57" selection-end-column="45" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/circle/network/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2057">
<caret line="138" column="34" lean-forward="false" selection-start-line="138" selection-start-column="34" selection-end-line="138" selection-end-column="34" />
<folding />
</state>
</provider>
......
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.core.exceptions import PermissionDenied
class CheckedObjectMixin(object):
read_level = 'user'
def get_object(self, **kwargs):
obj = super(CheckedObjectMixin, self).get_object()
if not obj.has_level(self.request.user, self.read_level):
raise PermissionDenied()
return obj
......@@ -22,6 +22,7 @@
"favico.js": "~0.3.5",
"datatables": "~1.10.4",
"chart.js": "2.3.0",
"clipboard": "~1.6.1"
"clipboard": "~1.6.1",
"jsPlumb": "2.5.7"
}
}
No preview for this file type
......@@ -548,4 +548,8 @@ POLICY_FILES = {
POLICY_DIRS = {
'compute': ['nova_policy.d'],
'volume': ['cinder_policy.d'],
}
\ No newline at end of file
}
DEFAULT_USERNET_VLAN_NAME = (
get_env_variable("DEFAULT_USERNET_VLAN_NAME", "usernet"))
USERNET_MAX = 2 ** 12
......@@ -110,6 +110,7 @@ if DEBUG:
PIPELINE["COMPILERS"] = (
'dashboard.compilers.DummyLessCompiler',
'pipeline.compilers.es6.ES6Compiler',
)
ADMIN_ENABLED = True
......
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
""" Static files and pipeline configuration. """
# flake8: noqa
from os.path import abspath, dirname, join, normpath, isfile, exists
from util import get_env_variable
########## PATH CONFIGURATION
# Absolute filesystem path to the Django project directory:
BASE_DIR = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(BASE_DIR)
########## MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = normpath(join(SITE_ROOT, 'media'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = get_env_variable('DJANGO_MEDIA_URL', default='/media/')
########## END MEDIA CONFIGURATION
########## STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = normpath(join(SITE_ROOT, 'static_collected'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = get_env_variable('DJANGO_STATIC_URL', default='/static/')
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'pipeline.finders.PipelineFinder',
)
########## END STATIC FILE CONFIGURATION
STATICFILES_DIRS = [normpath(join(SITE_ROOT, 'bower_components'))]
p = normpath(join(SITE_ROOT, '../../site-circle/static'))
if exists(p):
STATICFILES_DIRS.append(p)
STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
PIPELINE = {
'COMPILERS' : ('pipeline.compilers.less.LessCompiler',
'pipeline.compilers.es6.ES6Compiler', ),
'LESS_ARGUMENTS': u'--include-path={}'.format(':'.join(STATICFILES_DIRS)),
'CSS_COMPRESSOR': 'pipeline.compressors.yuglify.YuglifyCompressor',
'BABEL_ARGUMENTS': u'--presets env',
'JS_COMPRESSOR': None,
'DISABLE_WRAPPER': True,
'STYLESHEETS': {
"all": {
"source_filenames": (
"compile_bootstrap.less",
"bootstrap/dist/css/bootstrap-theme.css",
"fontawesome/css/font-awesome.css",
"jquery-simple-slider/css/simple-slider.css",
"intro.js/introjs.css",
"template.less",
"dashboard/dashboard.less",
"network/network.less",
"autocomplete_light/vendor/select2/dist/css/select2.css",
"autocomplete_light/select2.css",
),
"output_filename": "all.css",
},
"network-editor": {
"source_filenames": (
"network/editor.less",
),
"output_filename": "network-editor.css",
},
},
'JAVASCRIPT': {
"all": {
"source_filenames": (
# "jquery/dist/jquery.js", # included separately
"bootbox/bootbox.js",
"bootstrap/dist/js/bootstrap.js",
"intro.js/intro.js",
"jquery-knob/dist/jquery.knob.min.js",
"jquery-simple-slider/js/simple-slider.js",
"favico.js/favico.js",
"datatables/media/js/jquery.dataTables.js",
"autocomplete_light/jquery.init.js",
"autocomplete_light/autocomplete.init.js",
"autocomplete_light/vendor/select2/dist/js/select2.js",
"autocomplete_light/select2.js",
"jsPlumb/dist/js/dom.jsPlumb-1.7.5-min.js",
"dashboard/dashboard.js",
"dashboard/activity.js",
"dashboard/group-details.js",
"dashboard/group-list.js",
"dashboard/js/stupidtable.min.js", # no bower file
"dashboard/node-create.js",
"dashboard/node-details.js",
"dashboard/node-list.js",
"dashboard/profile.js",
"dashboard/store.js",
"dashboard/template-list.js",
"dashboard/vm-common.js",
"dashboard/vm-create.js",
"dashboard/vm-list.js",
"dashboard/help.js",
"js/host.js",
"js/network.js",
"js/switch-port.js",
"js/host-list.js",
),
"output_filename": "all.js",
},
"vm-detail": {
"source_filenames": (
"clipboard/dist/clipboard.min.js",
"dashboard/vm-details.js",
"no-vnc/include/util.js",
"no-vnc/include/webutil.js",
"no-vnc/include/base64.js",
"no-vnc/include/websock.js",
"no-vnc/include/des.js",
"no-vnc/include/keysym.js",
"no-vnc/include/keysymdef.js",
"no-vnc/include/keyboard.js",
"no-vnc/include/input.js",
"no-vnc/include/display.js",
"no-vnc/include/jsunzip.js",
"no-vnc/include/rfb.js",
"dashboard/vm-console.js",
"dashboard/vm-tour.js",
),
"output_filename": "vm-detail.js",
},
"datastore": {
"source_filenames": (
"chart.js/dist/Chart.min.js",
"dashboard/datastore-details.js"
),
"output_filename": "datastore.js",
},
"network-editor": {
"source_filenames": (
"jsPlumb/dist/js/jsplumb.min.js",
"network/editor.es6",
),
"output_filename": "network-editor.js",
},
},
}
# Copyright 2017 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from os import environ
from django.core.exceptions import ImproperlyConfigured
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
def get_env_variable(var_name, default=None):
""" Get the environment variable or return exception/default """
try:
return environ[var_name]
except KeyError:
if default is None:
error_msg = "Set the %s environment variable" % var_name
raise ImproperlyConfigured(error_msg)
else:
return default
......@@ -30,7 +30,7 @@ utils.patch_middleware_get_user()
urlpatterns = [
url(r'^$', lambda x: redirect(reverse("dashboard.index"))),
# url(r'^network/', include('network.urls')),
url(r'^network/', include('network.urls')),
# url(r'^blacklist-add/', add_blacklist_item),
url(r'^dashboard/', include('dashboard.urls')),
url(r'^request/', include('request.urls')),
......
......@@ -19,8 +19,11 @@ from sys import exc_info
import logging
from django.shortcuts import render_to_response
from django.shortcuts import render_to_response, redirect
from django.contrib import messages
from django.template import RequestContext
from django.http import JsonResponse
from django.utils.translation import ugettext_lazy as _
from .models import HumanReadableException
......@@ -59,3 +62,29 @@ def handler403(request):
resp = render_to_response("403.html", ctx)
resp.status_code = 403
return resp
class CreateLimitedResourceMixin(object):
resource_name = None
model = None
profile_attribute = None
def post(self, *args, **kwargs):
user = self.request.user
try:
limit = getattr(user.profile, self.profile_attribute)
except Exception as e:
logger.debug('No profile or %s: %s', self.profile_attribute, e)
else:
current = self.model.objects.filter(owner=user).count()
logger.debug('%s current use: %d, limit: %d',
self.resource_name, current, limit)
if current > limit:
messages.error(self.request,
_('%s limit (%d) exceeded.')
% (self.resource_name, limit))
if self.request.is_ajax():
return JsonResponse({'redirect': '/'})
else:
return redirect('/')
return super(CreateLimitedResourceMixin, self).post(*args, **kwargs)
......@@ -947,16 +947,27 @@ class VmRemoveInterfaceForm(OperationForm):
class VmAddInterfaceForm(OperationForm):
network_type = 'vlan'
label = _('Vlan')
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
super(VmAddInterfaceForm, self).__init__(*args, **kwargs)
field = forms.ModelChoiceField(
queryset=choices, required=True, label=_('Vlan'))
queryset=choices, required=False,
label=self.label)
if not choices:
field.widget.attrs['disabled'] = 'disabled'
field.empty_label = _('No more networks.')
self.fields['vlan'] = field
self.fields[self.network_type] = field
class VmAddUserInterfaceForm(VmAddInterfaceForm):
network_type = 'vxlan'
label = _('Vxlan')
class DeployChoiceField(forms.ModelChoiceField):
......@@ -1298,6 +1309,9 @@ class UserEditForm(forms.ModelForm):
instance_limit = forms.IntegerField(
label=_('Instance limit'),
min_value=0, widget=NumberInput)
network_limit = forms.IntegerField(
label=_('Virtual network limit'),
min_value=0, widget=NumberInput)
two_factor_secret = forms.CharField(
label=_('Two-factor authentication secret'),
help_text=_("Remove the secret key to disable two-factor "
......@@ -1307,18 +1321,22 @@ class UserEditForm(forms.ModelForm):
super(UserEditForm, self).__init__(*args, **kwargs)
self.fields["instance_limit"].initial = (
self.instance.profile.instance_limit)
self.fields["network_limit"].initial = (
self.instance.profile.network_limit)
self.fields["two_factor_secret"].initial = (
self.instance.profile.two_factor_secret)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'instance_limit',
'is_active', "two_factor_secret", )
'network_limit', 'is_active', 'two_factor_secret', )
def save(self, commit=True):
user = super(UserEditForm, self).save()
user.profile.instance_limit = (
self.cleaned_data['instance_limit'] or None)
user.profile.network_limit = (
self.cleaned_data['network_limit'] or None)
user.profile.two_factor_secret = (
self.cleaned_data['two_factor_secret'] or None)
user.profile.save()
......
......@@ -125,6 +125,11 @@ class Command(BaseCommand):
vm.snat_to.add(net)
vm.snat_to.add(vm)
# Add unmanged vlan for user networks
self.create(Vlan, 'vid', name='usernet', vid=5,
network4='0.0.0.0/0', domain=vm_domain,
managed=False)
# default vlan groups
vg_all = self.create(VlanGroup, 'name', name='all')
vg_all.vlans.add(vm, man, net)
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-10 00:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0006_auto_20170707_1909'),
]
operations = [
migrations.AddField(
model_name='profile',
name='network_limit',
field=models.IntegerField(default=2, verbose_name=b'Virtual network limit'),
),
]
......@@ -178,6 +178,8 @@ class Profile(Model):
unique=True, blank=True, null=True, max_length=64,
help_text=_('Unique identifier of the person, e.g. a student number.'))
instance_limit = IntegerField(default=5)
network_limit = IntegerField(default=2,
verbose_name="Virtual network limit")
use_gravatar = BooleanField(
verbose_name=_("Use Gravatar"), default=True,
help_text=_("Whether to use email address as Gravatar profile image"))
......
......@@ -28,7 +28,7 @@ $(function() {
});
/* operations */
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) {
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, #vm-details-add-user-interface, .operation-wrapper').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin');
$.ajax({
......
......@@ -223,6 +223,7 @@ $(function () {
register_search($("#dashboard-group-search-form"), $("#dashboard-group-list"), generateGroupHTML);
register_search($("#dashboard-user-search-form"), $("#dashboard-user-list"), generateUserHTML);
register_search($("#dashboard-template-search-form"), $("#dashboard-template-list"), generateTemplateHTML);
register_search($("#dashboard-vxlan-search-form"), $("#dashboard-vxlan-list"), generateVxlanHTML);
/* notification message toggle */
$(document).on('click', ".notification-message-subject", function() {
......@@ -331,6 +332,18 @@ function generateNodeHTML(data, is_last) {
'</a>';
}
function generateVxlanHTML(data, is_last) {
html = '<a href="' + data.url + '" class="list-group-item real-link' + (is_last ? ' list-group-item-last' : '') + '">' +
'<span class="index-vxlan-list-name">' +
'<i class="fa ' + data.icon + '" title="' + data.name + '"></i> ' + safe_tags_replace(data.name);
if(data.vni !== null && data.vni !== undefined)
html += ' <small class="text-muted"> vni: ' + data.vni + '</small>';
html += '</span>' +
'<div style="clear: both;"></div>' +
'</a>';
return html;
}
/* copare vm-s by fav, pk order */
function compareVmByFav(a, b) {
if(a.fav && b.fav) {
......
......@@ -584,7 +584,8 @@ footer a, footer a:hover, footer a:visited {
}
#dashboard-vm-list, #dashboard-node-list, #dashboard-group-list,
#dashboard-template-list, #dashboard-files-toplist, #dashboard-user-list {
#dashboard-template-list, #dashboard-files-toplist, #dashboard-user-list,
#dashboard-vxlan-list {
min-height: 200px;
}
......
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
<span class="btn btn-default btn-xs infobtn pull-right" data-container="body" title="{% trans "List of virtual networks that are available for you." %}">
<i class="fa fa-info-circle"></i>
</span>
<a href="{% url "network.editor" %}" class="btn btn-default btn-xs pull-right" data-container="body" title="{% trans "Edit network topology." %}">
<i class="fa fa-pencil-square-o"></i> Editor
</a>
<h3 class="no-margin"><i class="fa fa-globe"></i> {% trans "Virtual networks" %}
</h3>
</div>
<div class="list-group" id="vxlan-list-view">
<div id="dashboard-vxlan-list">
{% for vxlan in vxlans %}
<a href="{% url "network.vxlan" vni=vxlan.vni %}" class="list-group-item
{% if forloop.last and vxlan|length < 5 %} list-group-item-last{% endif %}">
<span class="index-vxlan-list-name">
<i class="fa fa-sitemap"></i> {{ vxlan.name }}
{% if user.is_superuser %}
<small class="text-muted"> vni: {{ vxlan.vni }} </small>
{% endif %}
</span>
</a>
{% endfor %}
</div>
<div class="list-group-item list-group-footer">
<div class="row">
<div class="col-xs-5 col-sm-6">
<form action="{% url "network.vxlan-list" %}" method="GET" id="dashboard-vxlan-search-form">
<div class="input-group input-group-sm">
<input name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
</div>
<div class="col-xs-7 col-sm-6 text-right">
<a href="{% url "network.vxlan-list" %}" class="btn btn-primary btn-xs">
<i class="fa fa-chevron-circle-right"></i> {% trans "show all" %}
</a>
<a href="{% url "network.vxlan-create" %}" class="btn btn-success btn-xs">
<i class="fa fa-plus-circle"></i> {% trans "new" %}
</a>
</div>
</div>
</div>
</div>
</div>
......@@ -48,6 +48,11 @@
{# {% include "dashboard/index-users.html" %}#}
{# </div>#}
{# {% endif %}#}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vxlans.html" %}
</div>
</div>
</div>
{% endblock %}
......@@ -8,6 +8,15 @@
<i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a>
{% endif %}{% endwith %}
</div>
<br />
<br />
<div id="vm-details-add-user-interface">
{% with op=op.add_user_interface %}{% if op %}
<a href="{{op.get_url}}" class="btn btn-{{op.effect}} operation pull-right"
{% if op.disabled %}disabled{% endif %}>
<i class="fa fa-{{op.icon}}"></i> {% trans "add user interface" %}</a>
{% endif %}{% endwith %}
</div>
<h2>
{% trans "Interfaces" %}
</h2>
......@@ -16,12 +25,28 @@
{% for i in instance.interface_set.all %}
<div>
<h3 class="list-group-item-heading dashboard-vm-details-network-h3">
<i class="fa fa-{% if i.host %}globe{% else %}link{% endif %}"></i> {{ i.vlan.name }}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
<i class="fa fa-{% if i.host %}globe{% else %}link{% endif %}"></i>
{% if i.vxlan %}
{{ i.vxlan.name }} (user)
{% else %}
{{ i.vlan.name }}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
{% endif %}
{% if user.is_superuser and i.host %}
<a href="{{ i.host.get_absolute_url }}"
class="btn btn-default btn-xs">{% trans "edit" %}</a>
{% endif %}
{% if i.vxlan %}
{% with op=op.remove_user_interface %}{% if op %}
<span class="operation-wrapper">
<a href="{{op.get_url}}?interface={{ i.pk }}"
class="btn btn-{{op.effect}} btn-xs operation interface-remove"
{% if op.disabled %}disabled{% endif %}>{% trans "remove" %}
</a>
</span>
{% endif %}{% endwith %}
{% else %}
{% with op=op.remove_interface %}{% if op %}
<span class="operation-wrapper">
<a href="{{op.get_url}}?interface={{ i.pk }}"
......@@ -30,6 +55,7 @@
</a>
</span>
{% endif %}{% endwith %}
{% endif %}
</h3>
{% if i.host %}
<div class="row">
......
......@@ -15,6 +15,9 @@
<!--<i class="fa fa-refresh fa-spin fa-2x"></i>-->
</div>
<a class="pull-right btn btn-success btn-xs vm-create" href="{% url "dashboard.views.vm-create" %}"><i class="fa fa-plus-circle"></i> {% trans "new virtual machine" %}</a>
<a href="{% url "network.editor" %}" class="btn btn-primary btn-xs pull-right" data-container="body" title="{% trans "Edit network topology." %}">
<i class="fa fa-pencil-square-o"></i> Edit topology
</a>
<h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Virtual machines" %}</h3>
</div>
<div class="panel-body">
......
......@@ -25,6 +25,8 @@ from django.contrib.auth.models import Group, User
from django.core.cache import cache
from django.views.generic import TemplateView
from vm.models import Instance, Node, InstanceTemplate
from dashboard.views.vm import vm_ops
from network.models import Vxlan
from ..store_api import Store
......
......@@ -20,13 +20,14 @@ from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput, Field
from crispy_forms.bootstrap import FormActions, FieldWithButtons, StrictButton
from firewall.models import (
Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
SwitchPort, Firewall
)
from network.models import Vxlan
class LinkButton(BaseInput):
......@@ -348,3 +349,53 @@ class VlanGroupForm(ModelForm):
class Meta:
model = VlanGroup
fields = ("name", "vlans", "description", "owner", )
class VxlanSuperUserForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
Div(
Fieldset(
'',
'name',
'vni',
'vlan',
'description',
'comment',
'owner',
)
),
FormActions(
Submit('submit', _('Save')),
LinkButton('back', _('Back'), reverse_lazy(
'network.vxlan-list'))
)
)
class Meta:
model = Vxlan
fields = ('name', 'vni', 'vlan', 'description', 'comment', 'owner', )
class VxlanForm(ModelForm):
helper = FormHelper()
helper.layout = Layout(
Div(
Fieldset(
'',
'name',
'description',
'comment',
Field('vni', type='hidden'),
)
),
FormActions(
Submit('submit', _('Save')),
LinkButton('back', _('Back'), reverse_lazy(
'network.vxlan-list'))
)
)
class Meta:
model = Vxlan
fields = ('name', 'description', 'comment', 'vni', )
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-25 18:56
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import firewall.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('firewall', '0006_auto_20170707_1909'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Vxlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vni', models.IntegerField(help_text='VXLAN Network Identifier.', unique=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(16777215)], verbose_name='VNI')),
('name', models.CharField(help_text='The short name of the virtual network.', max_length=20, unique=True, validators=[firewall.fields.val_alfanum], verbose_name='Name')),
('description', models.TextField(blank=True, help_text='Description of the goals and elements of the virtual network.', verbose_name='description')),
('comment', models.TextField(blank=True, help_text='Notes, comments about the network', verbose_name='comment')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('modified_at', models.DateTimeField(auto_now=True, verbose_name='modified at')),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='owner')),
('vlan', models.ForeignKey(help_text='The server vlan.', on_delete=django.db.models.deletion.CASCADE, to='firewall.Vlan', verbose_name='vlan')),
],
options={
'ordering': ('vni',),
'verbose_name': 'vxlan',
'verbose_name_plural': 'vxlans',
},
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-10 00:41
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import firewall.fields
class Migration(migrations.Migration):
dependencies = [
('network', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='vxlan',
options={'ordering': ('vni',), 'permissions': (('create_vxlan', 'Can create a Vxlan network.'),), 'verbose_name': 'vxlan', 'verbose_name_plural': 'vxlans'},
),
migrations.AlterField(
model_name='vxlan',
name='name',
field=models.CharField(help_text='The short name of the virtual network.', max_length=20, validators=[firewall.fields.val_alfanum], verbose_name='Name'),
),
migrations.AlterField(
model_name='vxlan',
name='vni',
field=models.IntegerField(help_text='VXLAN Network Identifier.', unique=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(4095)], verbose_name='VNI'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-30 01:01
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('network', '0002_auto_20171110_0041'),
]
operations = [
migrations.CreateModel(
name='EditorElement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('x', models.IntegerField()),
('y', models.IntegerField()),
('free_port_num', models.IntegerField()),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
......@@ -15,4 +15,103 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# Create your models here.
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.fields import (
GenericRelation, GenericForeignKey
)
from django.contrib.contenttypes.models import ContentType
from firewall.models import Vlan
from firewall.fields import val_alfanum
from openstack_auth.user import User
class EditorElement(models.Model):
x = models.IntegerField()
y = models.IntegerField()
free_port_num = models.IntegerField()
owner = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
def as_data(self):
type = 'network' if isinstance(self.content_object, Vxlan) else 'vm'
if type == 'network':
id = "net-%s" % self.content_object.vni
else:
id = "vm-%s" % self.content_object.pk
return {
'name': unicode(self.content_object),
'id': id,
'x': self.x,
'y': self.y,
'free_port_num': self.free_port_num,
'type': type,
'description': self.content_object.description,
}
class Vxlan(models.Model):
"""
A virtual L2 network,
These networks are isolated by the vxlan (virtual extensible lan)
technology, which is commonly used by managed network switches
to partition the network, with a more scalable way opposite vlan
technology. Usually, it used over vlan networks.
Each vxlan network has a unique identifier (VNI), a name, and
a server vlan network.
"""
# NOTE: VXLAN VNI's maximal value is 2^24-1, but MAC address generator
# only supports 2^12-1 maximal value.
vni = models.IntegerField(unique=True,
verbose_name=_('VNI'),
help_text=_('VXLAN Network Identifier.'),
validators=[MinValueValidator(0),
MaxValueValidator(2 ** 12 - 1)])
vlan = models.ForeignKey(Vlan,
verbose_name=_('vlan'),
help_text=_('The server vlan.'))
name = models.CharField(max_length=20,
verbose_name=_('Name'),
help_text=_('The short name of the '
'virtual network.'),
validators=[val_alfanum])
description = models.TextField(blank=True, verbose_name=_('description'),
help_text=_(
'Description of the goals and elements '
'of the virtual network.'))
comment = models.TextField(blank=True,
verbose_name=_('comment'),
help_text=_(
'Notes, comments about the network'))
created_at = models.DateTimeField(auto_now_add=True,
verbose_name=_('created at'))
owner = models.ForeignKey(User, blank=True, null=True,
verbose_name=_('owner'))
modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at'))
editor_elements = GenericRelation(EditorElement)
class Meta:
app_label = 'network'
verbose_name = _("vxlan")
verbose_name_plural = _("vxlans")
ordering = ('vni', )
permissions = (
('create_vxlan', _('Can create a Vxlan network.')),
)
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse('network.vxlan', kwargs={'vni': self.vni})
/* jshint esversion: 6 */
function renderListElement(elem){
return `
<div class="unused-element"
type="${ elem.type }"
description="${ elem.description }"
id="${ elem.id }"
icon="${ elem.icon }"
name="${ elem.name }"
free_port_num="${ elem.free_port_num }">
<i class="fa ${ elem.type == 'vm' ? 'fa-desktop' : 'fa-sitemap' }"></i>
${ elem.name }
</div>`;
}
function renderBoardElement(elem){
return `
<div class="element"
name="${ elem.name }"
type="${ elem.type }"
id="${ elem.id }"
description="${ elem.description }"
icon="${ elem.icon }"
free_port_num="${ elem.free_port_num }"
ondragstart="return false;">
<i class="fa ${ elem.type == 'vm' ? 'fa-desktop' : 'fa-sitemap'}"></i>
${ elem.name }
</div>`;
}
var add_interfaces = [];
var remove_interfaces = [];
var old_connections = [];
var add_nodes = new Set();
var remove_nodes = new Set();
var old_nodes = new Set();
function convertConnection(connection) {
var con = {
source: connection.source.id,
target: connection.target.id,
equals: function(other) {
return this.source === other.source &&
this.target === other.target;
}
};
if(con.source.startsWith('net')){
var tmp = con.source;
con.source = con.target;
con.target = tmp;
}
con.source = con.source.slice(3);
con.target = con.target.slice(4);
return con;
}
function uniqueAddToList(list, value){
var hit = list.find((val) => {
return val.equals(value);
});
if(hit) return;
list.push(value);
}
function removeFromList(list, value){
var index = list.findIndex((val) => {
return val.equals(value);
});
if(index === -1) return;
list.splice(index, 1);
}
function isOldConnection(connection){
return old_connections.find((val) => {
return val.equals(connection);
});
}
function cleanListElements(list){
return list.map((value) => {
return {
source: value.source,
target: value.target
};
});
}
function addInterface(connection) {
var con = convertConnection(connection);
if(!isOldConnection(con))
uniqueAddToList(add_interfaces, con);
removeFromList(remove_interfaces, con);
}
function removeInterface(connection) {
var con = convertConnection(connection);
if(isOldConnection(con))
uniqueAddToList(remove_interfaces, con);
removeFromList(add_interfaces, con);
}
function getNodeId(node) {
return node.id;
}
function isOldNode(id) {
return old_nodes.has(id);
}
function addNode(node) {
var id = getNodeId(node);
add_nodes.add(id);
remove_nodes.delete(id);
}
function removeNode(node) {
var id = getNodeId(node);
if(isOldNode(id))
remove_nodes.add(id);
add_nodes.delete(id);
}
function checkLoopback(connection) {
return connection.target !== connection.source;
}
function checkCompatibility(connection) {
var target_type = $(connection.target).attr('type');
var source_type = $(connection.source).attr('type');
return target_type !== source_type;
}
// Before the connection is established
function beforeDrop(info) {
return checkCompatibility(info.connection);
}
function onConnect(info) {
addInterface(info.connection);
}
function onDetach(info) {
removeInterface(info.connection);
}
function FakeConnection(sourceId, targetId) {
this.source = {id: sourceId};
this.target = {id: targetId};
return this;
}
function onConnectionMoved(info) {
var fakeOrigCon = new FakeConnection(info.originalSourceId,
info.originalTargetId);
removeInterface(fakeOrigCon);
var fakeNewCon = new FakeConnection(info.newSourceId,
info.newTargetId);
addInterface(fakeNewCon);
}
function onConnectionAborted(connection) {
addInterface(connection);
}
function randInt(from, to) {
if(from > to){
var tmp = to;
to = from;
from = tmp;
}
var size = to - from;
return Math.floor((Math.random() * size) + from);
}
function convertElement(elem) {
return {
id: elem.attr('id'),
type: elem.attr('type'),
description: elem.attr('description'),
name: elem.attr('name'),
icon: elem.attr('icon'),
free_port_num: elem.attr('free_port_num')
};
}
function convertListForSaving(list) {
const getId = (id, type) =>
(type === 'vm') ? id.slice(3) : id.slice(4);
var retv = [];
list.forEach( (i, id) => {
var e=$('#' + id);
retv.push({
id: getId(e.attr('id'), e.attr('type')),
type: e.attr('type'),
x: e.css('left').replace('px', ''),
y: e.css('top').replace('px', ''),
free_port_num: e.attr('free_port_num')
});
});
return retv;
}
class SwitchButton {
constructor(id, checked) {
this.element = $('#' + id);
this.element.css('cursor', 'pointer');
this.afterChanged(() => {});
this.setClass(checked);
}
setClass(checked){
this.element.removeClass('fa');
this.element.removeClass('fa-toggle-on');
this.element.removeClass('fa-toggle-off');
this.element.addClass('fa');
this.element.addClass( checked ? 'fa-toggle-on' : 'fa-toggle-off');
}
isChecked() {
return this.element.hasClass('fa-toggle-on');
}
afterChanged(func) {
this.element.off('click');
var thiz = this;
this.element.click(function() {
thiz.setClass(!thiz.isChecked());
func();
});
}
}
jsPlumb.ready(() => {
var endpointOptions = {
anchor: 'Continuous',
isSource: true,
isTarget: true,
maxConnections: 1
};
var jsPlumbInstance = jsPlumb;
jsPlumbInstance.setContainer('#dropContainer');
jsPlumbInstance.bind('beforeDrop', beforeDrop);
jsPlumbInstance.bind('connection', onConnect);
jsPlumbInstance.bind('connectionDetached', onDetach);
jsPlumbInstance.bind('connectionMoved', onConnectionMoved);
jsPlumbInstance.bind('connectionAborted', onConnectionAborted);
function connectEndpoints(connection) {
return jsPlumbInstance.connect(connection, {
allowLoopback: false,
newConnection: true,
anchor: 'Continuous',
deleteEndpointsOnDetach: true
});
}
function addEndpoint(elem){
jsPlumbInstance.addEndpoint(elem.id, endpointOptions);
}
function generatePosition(){
var dc = $('#dropContainer');
var width = dc.css("width").replace('px', '');
var height = dc.css("height").replace('px', '');
return {
x: randInt(0, width),
y: randInt(0, height)
};
}
function addElementToBoard(element, random) {
var pos;
if(random)
pos = generatePosition();
else
pos = {
x: element.x,
y: element.y
};
var newe = $(renderBoardElement(element))
.css('top', pos.y + 'px')
.css('left', pos.x + 'px')[0];
$('#dropContainer').append(newe);
for(var i = 0; i < element.free_port_num; ++i){
addEndpoint(newe);
}
jsPlumbInstance.draggable(newe.id, {containment: true});
jsPlumbInstance.repaint(newe.id);
$(newe).bind('contextmenu', removeElement);
jsPlumbInstance.repaintEverything();
}
function selectElementFromList(ev) {
var elem = $(ev.target);
var obj = convertElement(elem);
elem.detach();
addElementToBoard(obj, true);
addNode(obj);
}
function addElementToList(elem) {
$('#dragContainer').append(renderListElement(elem));
$('#' + elem.id).click(selectElementFromList);
}
function removeElement(ev) {
var elem = $(ev.target);
var obj = convertElement(elem);
jsPlumbInstance.removeAllEndpoints(elem.attr('id'));
elem.detach();
addElementToList(obj);
removeNode(obj);
}
function clearWorkspace() {
jsPlumbInstance.deleteEveryConnection();
jsPlumbInstance.deleteEveryEndpoint();
$('.element').detach();
$('.unused-element').detach();
}
function initialize(result){
clearWorkspace();
old_nodes = new Set();
add_nodes = new Set();
remove_nodes = new Set();
$.each(result.elements, (i, element) => {
addElementToBoard(element);
old_nodes.add(element.id);
add_nodes.add(element.id);
});
$.each(result.nongraph_elements, (i, element) => {
addElementToBoard(element, true);
add_nodes.add(element.id);
});
$.each(result.unused_elements, (i, element) => {
addElementToList(element);
});
old_connections = [];
$.each(result.connections, (i, connection) => {
var con = connectEndpoints(connection);
old_connections.push(convertConnection(con));
});
add_interfaces = []; // Because of a 'connect' event,
// connections are added to this list,
// but this is not necessary.
remove_interfaces = [];
}
function save() {
var data = {
add_interfaces: cleanListElements(add_interfaces),
remove_interfaces: cleanListElements(remove_interfaces),
add_nodes: convertListForSaving(add_nodes),
remove_nodes: convertListForSaving(remove_nodes)
};
$.post('', JSON.stringify(data), initialize, 'json');
}
$.get('', initialize);
$("#saveButton").click(save);
$('#searchField').on('keyup', filter);
var vmFilter = new SwitchButton('vm-filter', true);
vmFilter.afterChanged(filter);
var netFilter = new SwitchButton('net-filter', true);
netFilter.afterChanged(filter);
function filter() {
$(".unused-element").each((i, elem) => {
elem = $(elem);
elem.hide();
var key = $("#searchField").val().toLowerCase();
var type = elem.attr('type');
var network_on = netFilter.isChecked();
var vm_on = vmFilter.isChecked();
if(elem.attr("name").toLowerCase().indexOf(key) >= 0 &&
((type === "network" && network_on) || (type === "vm" && vm_on)))
elem.show();
});
}
});
.flex-container {
display: flex;
flex-direction: row;
}
#filterConatiner {
margin-left: 10px;
margin-right: 10px;
}
#dragPanel {
flex: 250px;
width: 250px;
margin: 0px;
overflow: hidden;
height: 700px;
}
#dragContainer{
overflow-y: scroll;
height: 574px;
}
#dropContainer{
position: relative !important;
text-align: justify !important;
flex: auto;
width: 90%;
height: auto;
background: grey;
resize: none;
}
.unused-element {
cursor: pointer;
}
.element {
position: absolute;
left: 10px;
display: inline;
line-height: 75px;
text-align: center;
vertical-align: middle;
color: black;
font-size: 20px;
height: 75px;
padding-left: 10px;
padding-right: 10px;
border-radius: 8px;
background: white;
-webkit-border-radius: 8px;
border-style: solid;
border-width: 1px;
z-index: 40;
cursor: pointer;
}
.container {
margin: 10px;
width: 100%;
padding: 0px;
}
body {
height: 100%;
}
......@@ -24,6 +24,8 @@ from django_tables2.columns import (LinkColumn, TemplateColumn, Column,
BooleanColumn)
from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort
from network.models import Vxlan
from vm.models import Interface
class MACColumn(Column):
......@@ -233,6 +235,16 @@ class VlanGroupTable(Table):
order_by = 'name'
class VxlanTable(Table):
name = LinkColumn('network.vxlan', args=[A('vni')])
class Meta:
model = Vxlan
attrs = {'class': 'table table-striped table-condensed'}
fields = ('vni', 'name', )
order_by = 'vni'
class HostRecordsTable(Table):
fqdn = LinkColumn(
"network.record", args=[A("pk")],
......@@ -282,3 +294,16 @@ class FirewallRuleTable(Table):
'action', 'proto', 'actions')
order_by = '-pk'
empty_text = _("No related rules found.")
class SmallVmTable(Table):
instance = Column(accessor=A('instance.name'), verbose_name='VM')
instance_id = LinkColumn('dashboard.views.detail', args=[A('instance.pk')],
accessor=A('instance.pk'), verbose_name='ID')
class Meta:
model = Interface
attrs = {'class': 'table table-striped'}
fields = ('instance', )
sequence = ('instance_id', 'instance', )
order_by = 'instance.pk'
......@@ -29,7 +29,22 @@
</div>
{% trans "Vlans" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "802.1Q virtual networks" %}</small>
</div>
</h3>
<h3>
<div class="pull-right">
<a href="{% url "network.vxlan-list" %}" class="btn btn-xs btn-default">
<i class="fa fa-list"></i> {% trans "list" %}
</a>
<a href="{% url "network.vxlan-create" %}" class="btn btn-xs btn-success">
<i class="fa fa-plus-circle"></i> {% trans "new" %}
</a>
</div>
{% trans "Vxlans" %}
<div>
<small>{% trans "VXLAN virtual networks" %}</small>
</div>
</h3>
......@@ -44,7 +59,7 @@
</div>
{% trans "Domains" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Domain names" %}</small>
</div>
</h3>
......@@ -59,7 +74,7 @@
</div>
{% trans "Records" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "DNS records" %}</small>
</div>
</h3>
......@@ -74,7 +89,7 @@
</div>
{% trans "Blacklist items" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "List of denied IPs and exceptions" %}</small>
</div>
</h3>
......@@ -89,7 +104,7 @@
</div>
{% trans "Rules" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Firewall rules" %}</small>
</div>
</h3>
......@@ -104,7 +119,7 @@
</div>
{% trans "Switch ports" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Switch ports" %}</small>
</div>
</h3>
......@@ -119,7 +134,7 @@
</div>
{% trans "Vlan groups" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Groups of 802.1Q virtual networks" %}</small>
</div>
</h3>
......@@ -134,7 +149,7 @@
</div>
{% trans "Host groups" %}
<div>
<small>{% trans "Hosts are machines on the network" %}</small>
<small>{% trans "Groups of hosts are machines on the network" %}</small>
</div>
</h3>
</div>
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load pipeline %}
{% block title-page %}{% trans 'Network Editor' %}{% endblock %}
{% block extra_css %}
{% stylesheet "network-editor" %}
{% endblock %}
{% block content %}
<div class="flex-container" id="workspace">
<div class="panel panel-default text-center" id="dragPanel">
<div class="panel-heading">
<div class="row">
<div class="col-md-9 text-left">
<h3 class="no-margin"><i class="fa fa-sitemap"></i> {% trans 'Editor' %}</h3>
</div>
<div class="col-md-3 text-left">
<button class="btn btn-success btn-xs" id="saveButton"><i class="fa fa-floppy-o"></i></button>
</div>
</div>
</div>
<div class="panel-heading text-center">
<div id="filterConatiner">
<div class="row">
<input type="text" class="form-control" id="searchField" placeholder="{% trans 'Search' %}"/><br />
</div>
<div class="row">
<div class="col-md-6">
<i id="vm-filter"></i> <i class="fa fa-desktop"></i> vm
</div>
<div class="col-md-6">
<i id="net-filter"></i> <i class="fa fa-sitemap"></i> net
</div>
</div>
</div>
</div>
<div class="panel-body" id="dragContainer">
</div>
</div>
<div class="" id="dropContainer" oncontextmenu="return false;"></div>
</div>
{% endblock %}
{% block extra_js %}
{% javascript "network-editor" %}
{% endblock %}
{% extends "dashboard/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create" %} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<h2>{% trans "Create a new vxlan" %}</h2>
</div>
<div class="row">
<div class="col-sm-8">
{% crispy form %}
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{{ form.name.value }} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-delete" vni=vxlan.vni %}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this vxlan" %}</a>
<h2>{{ form.name.value }} <small>{% trans "details of vxlan" %}</small></h2>
</div>
<div class="row">
<div class="col-sm-6">
{% crispy form %}
</div>
<div class="col-sm-6">
<div class="page-header">
<h3>{% trans "Connected virtual machines" %}</h3>
</div>
{% render_table vm_list %}
<div class="page-header">
<h3>{% trans "Manage access" %}</h3>
</div>
{% include "dashboard/_manage_access.html" with table_id="vxlan-access-table" %}
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% block title-page %}{% trans "Vxlans" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-create" %}" class="btn btn-success pull-right"><i class="fa fa-plus-circle"></i> {% trans "Create a new vxlan" %}</a>
<a href="{% url "network.editor" %}" class="btn btn-primary pull-right" data-container="body" title="{% trans "Edit network topology." %}">
<i class="fa fa-pencil-square-o"></i> Edit topology
</a>
<h1>Vxlans <small>{% trans "list of all vxlans" %}</small></h1>
</div>
<div class="table-responsive">
{% render_table table %}
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create" %} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<h2>{% trans "Create a new vxlan" %}</h2>
</div>
<div class="row">
<div class="col-sm-8">
{% crispy form %}
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block title-page %}{{ form.name.value }} | {% trans "vxlan" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-delete" vni=vxlan.vni %}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this vxlan" %}</a>
<h2>{{ form.name.value }} <small>{% trans "details of vxlan" %}</small></h2>
</div>
<div class="row">
<div class="col-sm-6">
{% crispy form %}
</div>
<div class="col-sm-6">
<div class="page-header">
<h3>{% trans "Connected virtual machines" %}</h3>
</div>
{% render_table vm_list %}
<div class="page-header">
<h3>{% trans "Manage access" %}</h3>
</div>
{% include "dashboard/_manage_access.html" with table_id="vxlan-access-table" %}
</div>
</div>
{% endblock %}
{% extends "network/base.html" %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% block title-page %}{% trans "Vxlans" %}{% endblock %}
{% block content %}
<div class="page-header">
<a href="{% url "network.vxlan-create" %}" class="btn btn-success pull-right"><i class="fa fa-plus-circle"></i> {% trans "Create a new vxlan" %}</a>
<h1>Vxlans <small>{% trans "list of all vxlans" %}</small></h1>
</div>
<div class="table-responsive">
{% render_table table %}
</div>
{% endblock %}
......@@ -20,6 +20,7 @@ from .views import (
IndexView,
HostList, HostDetail, HostCreate, HostDelete,
VlanList, VlanDetail, VlanDelete, VlanCreate,
VxlanList, VxlanDetail, VxlanDelete, VxlanCreate, VxlanAclUpdateView,
DomainList, DomainDetail, DomainDelete, DomainCreate,
GroupList, GroupDetail, GroupDelete, GroupCreate,
RecordList, RecordDetail, RecordCreate, RecordDelete,
......@@ -30,7 +31,7 @@ from .views import (
FirewallList, FirewallDetail, FirewallCreate, FirewallDelete,
remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device,
VlanAclUpdateView
VlanAclUpdateView, NetworkEditorView
)
urlpatterns = [
......@@ -125,6 +126,19 @@ urlpatterns = [
url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(),
name="network.rule_delete"),
# vxlan
url('^vxlans/$', VxlanList.as_view(), name='network.vxlan-list'),
url('^vxlans/create$', VxlanCreate.as_view(), name='network.vxlan-create'),
url('^vxlans/(?P<vni>\d+)/$', VxlanDetail.as_view(), name='network.vxlan'),
url('^vxlans/(?P<pk>\d+)/acl/$', VxlanAclUpdateView.as_view(),
name='network.vxlan-acl'),
url('^vxlans/delete/(?P<vni>\d+)/$', VxlanDelete.as_view(),
name="network.vxlan-delete"),
# editor
url('^editor/$', NetworkEditorView.as_view(),
name="network.editor"),
# non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
name='network.remove_host_group'),
......
......@@ -15,32 +15,43 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import logging
import random
import json
from collections import OrderedDict
from netaddr import IPNetwork
from django.views.generic import (TemplateView, UpdateView, DeleteView,
CreateView)
from django.core.exceptions import ValidationError
from django.views.generic import (
TemplateView, UpdateView, DeleteView, CreateView,
)
from django.core.exceptions import (
ValidationError, PermissionDenied, ImproperlyConfigured
)
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, Http404
from django.db.models import Q
from django.conf import settings
from django_tables2 import SingleTableView
from firewall.models import (
Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
SwitchPort, EthernetDevice, Firewall)
from vm.models import Interface
SwitchPort, EthernetDevice, Firewall
)
from network.models import Vxlan, EditorElement
from vm.models import Interface, Instance
from common.views import CreateLimitedResourceMixin
from .tables import (
HostTable, VlanTable, SmallHostTable, DomainTable, GroupTable,
RecordTable, BlacklistItemTable, RuleTable, VlanGroupTable,
SmallRuleTable, SmallGroupRuleTable, SmallRecordTable, SwitchPortTable,
SmallDhcpTable, FirewallTable, FirewallRuleTable,
SmallDhcpTable, FirewallTable, FirewallRuleTable, VxlanTable, SmallVmTable,
)
from .forms import (
HostForm, VlanForm, DomainForm, GroupForm, RecordForm, BlacklistItemForm,
RuleForm, VlanGroupForm, SwitchPortForm, FirewallForm
RuleForm, VlanGroupForm, SwitchPortForm, FirewallForm,
VxlanForm, VxlanSuperUserForm,
)
from django.contrib import messages
......@@ -48,7 +59,6 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import FormMixin
from django.utils.translation import ugettext_lazy as _
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
# from django.db.models import Q
from operator import itemgetter
from itertools import chain
from dashboard.views import AclUpdateView
......@@ -72,6 +82,9 @@ except ImportError:
content_type=content_type)
logger = logging.getLogger(__name__)
class MagicMixin(object):
def get(self, *args, **kwargs):
......@@ -508,7 +521,7 @@ class HostDetail(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
class HostCreate(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView):
SuccessMessageMixin, CreateView):
model = Host
template_name = "network/host-create.html"
form_class = HostForm
......@@ -802,6 +815,7 @@ class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
slug_field = 'vid'
slug_url_kwarg = 'vid'
success_message = _(u'Succesfully modified vlan %(name)s.')
success_url = reverse_lazy('network.vlan_list')
def get_context_data(self, **kwargs):
context = super(VlanDetail, self).get_context_data(**kwargs)
......@@ -813,8 +827,6 @@ class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
context['aclform'] = AclUserOrGroupAddForm()
return context
success_url = reverse_lazy('network.vlan_list')
class VlanCreate(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView):
......@@ -916,6 +928,370 @@ class VlanGroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
return reverse_lazy('network.vlan_group_list')
class VxlanList(LoginRequiredMixin, SingleTableView):
model = Vxlan
table_class = VxlanTable
table_pagination = False
def get_template_names(self):
if self.request.user.is_superuser:
return ["network/vxlan-superuser-list.html"]
else:
return ["network/vxlan-list.html"]
def get_queryset(self):
return Vxlan.get_objects_with_level('user', self.request.user)
def get(self, *args, **kwargs):
if self.request.is_ajax():
return self._create_ajax_request()
return super(VxlanList, self).get(*args, **kwargs)
def _create_ajax_request(self):
vxlans = self.get_queryset()
vxlans = [{
'pk': i.pk,
'url': reverse_lazy('network.vxlan', args=[i.pk]),
'icon': 'fa-sitemap',
'name': i.name,
'vni': i.vni if self.request.user.is_superuser else None
} for i in vxlans]
return JsonResponse(list(vxlans), safe=False)
class VxlanAclUpdateView(AclUpdateView):
model = Vxlan
class VxlanDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): #TODO: check user
model = Vxlan
slug_field = 'vni'
slug_url_kwarg = 'vni'
success_message = _(u'Succesfully modified vlan %(name)s.')
success_url = reverse_lazy('network.vxlan-list')
def get_template_names(self):
if self.request.user.is_superuser:
return ["network/vxlan-superuser-edit.html"]
else:
return ["network/vxlan-edit.html"]
def get_form_class(self, is_post=False):
if self.request.user.is_superuser:
return VxlanSuperUserForm
return VxlanForm
def get_context_data(self, **kwargs):
context = super(VxlanDetail, self).get_context_data(**kwargs)
context['vm_list'] = SmallVmTable(self.object.vm_interface.all())
context['acl'] = AclUpdateView.get_acl_data(
self.object, self.request.user, 'network.vxlan-acl')
context['aclform'] = AclUserOrGroupAddForm()
return context
def post(self, *args, **kwargs):
if not self.object.has_level(self.request.user, 'owner'):
raise PermissionDenied()
return super(VxlanDetail, self).post(*args, **kwargs)
class VxlanCreate(LoginRequiredMixin, CreateLimitedResourceMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Vxlan
profile_attribute = 'network_limit'
resource_name = _('Virtual network')
success_message = _(u'Successfully created vxlan %(name)s.')
def get_template_names(self):
if self.request.user.is_superuser:
return ["network/vxlan-superuser-create.html"]
else:
return ["network/vxlan-create.html"]
def get_form_class(self, is_post=False):
if self.request.user.is_superuser:
return VxlanSuperUserForm
return VxlanForm
def get_initial(self):
initial = super(VxlanCreate, self).get_initial()
initial['vni'] = self._generate_vni()
return initial
def get_default_vlan(self):
vlan = Vlan.objects.filter(
name=settings.DEFAULT_USERNET_VLAN_NAME).first()
if vlan is None:
msg = (_('Cannot find server vlan: %s') %
settings.DEFAULT_USERNET_VLAN_NAME)
if self.request.user.is_superuser:
messages.error(self.request, msg)
logger.error(msg)
raise ImproperlyConfigured()
return vlan
def form_valid(self, form):
obj = form.save(commit=False)
obj.owner = self.request.user
obj.vlan = self.get_default_vlan()
try:
obj.full_clean()
obj.save()
obj.set_level(obj.owner, 'owner')
self.object = obj
except Exception as e:
msg = _('Unexpected error occured. '
'Please try again or contact administrator!')
messages.error(self.request, msg)
logger.exception(e)
return redirect(self.get_success_url())
def form_invalid(self, form):
# When multiple client get same VNI value
if 'vni' in form.errors.as_data():
messages.error(self.request, _('Cannot create virtual network.'
' Please try again.'))
return redirect('network.vxlan-create')
return super(VxlanCreate, self).form_invalid(form)
def _generate_vni(self):
if Vxlan.objects.count() == settings.USERNET_MAX:
msg = _('Cannot find unused VNI value. '
'Please contact administrator!')
messages.error(self.request, msg)
logger.error(msg)
else:
full_range = set(range(0, settings.USERNET_MAX))
used_values = {vni[0] for vni in Vxlan.objects.values_list('vni')}
free_values = full_range - used_values
return random.choice(list(free_values))
class VxlanDelete(LoginRequiredMixin, DeleteView): #TODO: check user
model = Vlan
read_level = 'owner'
def get_template_names(self):
if self.request.user.is_superuser:
return ["network/confirm/base_delete.html"]
else:
return ["dashboard/confirm/base-delete.html"]
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('network.vxlan-list')
def get_object(self, queryset=None):
""" we identify vlans by vid and not pk """
return Vxlan.objects.get(vni=self.kwargs['vni'])
def delete(self, request, *args, **kwargs):
if self.request.user.is_superuser:
self.object = self.get_object()
if unicode(self.object) != request.POST.get('confirm'):
messages.error(request, _(u"Object name does not match."))
return self.get(request, *args, **kwargs)
response = super(VxlanDelete, self).delete(request, *args, **kwargs)
messages.success(request, _(u"Vxlan successfully deleted."))
return response
def get_context_data(self, **kwargs):
context = super(VxlanDelete, self).get_context_data(**kwargs)
if self.request.user.is_superuser:
context['confirmation'] = True
return context
class NetworkEditorView(LoginRequiredMixin, TemplateView):
template_name = 'network/editor.html'
def get(self, *args, **kwargs):
if self.request.is_ajax():
connections = self._get_connections()
ngelements = self._get_nongraph_elements(connections)
ngelements = self._serialize_elements(ngelements)
connections = map(lambda con: {
'source': 'vm-%s' % con['source'].pk,
'target': 'net-%s' % con['target'].vni,
}, connections['connections'])
unused_elements = self._get_unused_elements()
unused_elements = self._serialize_elements(unused_elements)
return JsonResponse({
'elements': map(lambda e: e.as_data(),
EditorElement.objects.filter(
owner=self.request.user)),
'nongraph_elements': ngelements,
'unused_elements': unused_elements,
'connections': connections,
})
return super(NetworkEditorView, self).get(*args, **kwargs)
def post(self, *args, **kwargs):
data = json.loads(self.request.body)
add_ifs = data.get('add_interfaces', [])
remove_ifs = data.get('remove_interfaces', [])
add_nodes = data.get('add_nodes', [])
remove_nodes = data.get('remove_nodes', [])
# Add editor element
self._element_list_operation(add_nodes, self._update_element)
# Remove editor element
self._element_list_operation(remove_nodes, self._remove_element)
# Add interface
self._interface_list_operation(add_ifs, self._add_interface)
# Remove interface
self._interface_list_operation(remove_ifs, self._remove_interface)
return self.get(*args, **kwargs)
def _max_port_num_helper(self, model, attr_name):
if not hasattr(self, attr_name):
value = model.get_objects_with_level(
'user', self.request.user).count()
setattr(self, attr_name, value)
return getattr(self, attr_name)
@property
def vm_max_port_num(self):
return self._max_port_num_helper(Vxlan, '_vm_max_port_num')
@property
def vxlan_max_port_num(self):
return self._max_port_num_helper(Instance, '_vxlan_max_port_num')
def _vm_serializer(self, vm):
max_port_num = self.vm_max_port_num
vxlans = Vxlan.get_objects_with_level(
'user', self.request.user).values_list('pk', flat=True)
free_port_num = max_port_num - vm.interface_set.filter(
vxlan__pk__in=vxlans).count()
return {
'name': unicode(vm),
'id': 'vm-%s' % vm.pk,
'description': vm.description,
'type': 'vm',
'icon': 'fa-desktop',
'free_port_num': free_port_num,
}
def _vxlan_serializer(self, vxlan):
max_port_num = self.vxlan_max_port_num
vms = Instance.get_objects_with_level(
'user', self.request.user).values_list('pk', flat=True)
free_port_num = max_port_num - Interface.objects.filter(
vxlan=vxlan, instance__pk__in=vms).count()
return {
'name': vxlan.name,
'id': 'net-%s' % vxlan.vni,
'description': vxlan.description,
'type': 'network',
'icon': 'fa-sitemap',
'free_port_num': free_port_num,
}
def _get_unused_elements(self):
connections = self._get_connections()
vms = map(lambda vm: vm.id, connections['vms'])
vxlans = map(lambda vxlan: vxlan.vni, connections['vxlans'])
eelems = EditorElement.objects.filter(owner=self.request.user)
vm_query = Q(pk__in=vms) | Q(editor_elements__in=eelems)
vms = Instance.get_objects_with_level(
'user', self.request.user).exclude(vm_query)
vxlan_query = Q(vni__in=vms) | Q(editor_elements__in=eelems)
vxlans = Vxlan.get_objects_with_level(
'user', self.request.user).exclude(vxlan_query)
return {
'vms': vms,
'vxlans': vxlans,
}
def _get_nongraph_elements(self, connections):
return {
'vms': filter(lambda v: not v.editor_elements.exists(),
connections['vms']),
'vxlans': filter(lambda v: not v.editor_elements.exists(),
connections['vxlans']),
}
def _get_connections(self):
""" Returns connections and theirs participants. """
vms = Instance.get_objects_with_level('user', self.request.user)
connections = []
vm_set = set()
vxlan_set = set()
for vm in vms:
for intf in vm.interface_set.filter(vxlan__isnull=False):
vm_set.add(vm)
vxlan_set.add(intf.vxlan)
connections.append({
'source': vm,
'target': intf.vxlan,
})
return {
'connections': connections,
'vms': vm_set,
'vxlans': vxlan_set,
}
def _serialize_elements(self, elements):
return (map(self._vm_serializer, elements['vms']) +
map(self._vxlan_serializer, elements['vxlans']))
def _get_modifiable_object(self, model, connection,
attr_name, filter_attr):
value = connection.get(attr_name)
if value is not None:
value = model.get_objects_with_level(
'user', self.request.user).filter(
**{filter_attr: value}).first()
return value
def _element_list_operation(self, node_list, operation):
for e in node_list:
elem = dict(e)
type = elem.pop('type')
id = elem.pop('id')
model = Instance if type == 'vm' else Vxlan
filter = {'pk': id} if type == 'vm' else {'vni': id}
object = model.get_objects_with_level(
'user', self.request.user).get(**filter)
operation(object.editor_elements, elem)
def _update_element(self, elements, elem):
elements.update_or_create(owner=self.request.user,
defaults=elem)
def _remove_element(self, elements, elem):
elements.filter(owner=self.request.user).delete()
def _interface_list_operation(self, if_list, operation):
for con in if_list:
vm = self._get_modifiable_object(Instance, con, 'source', 'pk')
vxlan = self._get_modifiable_object(Vxlan, con, 'target', 'vni')
if vm and vxlan:
operation(vm, vxlan)
def _add_interface(self, vm, vxlan):
vm.add_user_interface(
user=self.request.user, vxlan=vxlan, system=vm.system)
def _remove_interface(self, vm, vxlan):
intf = vm.interface_set.filter(vxlan=vxlan).first()
if intf:
vm.remove_user_interface(
interface=intf, user=self.request.user, system=vm.system)
def remove_host_group(request, **kwargs):
host = Host.objects.get(pk=kwargs['pk'])
group = Group.objects.get(pk=kwargs['group_pk'])
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-05 20:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('network', '0001_initial'),
('vm', '0002_interface_model'),
]
operations = [
migrations.AddField(
model_name='interface',
name='vxlan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vm_interface', to='network.Vxlan', verbose_name='vxlan'),
),
migrations.AddField(
model_name='interfacetemplate',
name='vxlan',
field=models.ForeignKey(blank=True, help_text='Virtual network the interface belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='network.Vxlan', verbose_name='vxlan'),
),
migrations.AlterField(
model_name='interface',
name='vlan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vm_interface', to='firewall.Vlan', verbose_name='vlan'),
),
migrations.AlterField(
model_name='interfacetemplate',
name='vlan',
field=models.ForeignKey(blank=True, help_text='Network the interface belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='firewall.Vlan', verbose_name='vlan'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-26 11:59
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('vm', '0003_auto_20171105_2011'),
('vm', '0007_auto_20180226_1239'),
]
operations = [
]
......@@ -34,6 +34,7 @@ from django.db import IntegrityError
from django.dispatch import Signal
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django.contrib.contenttypes.fields import GenericRelation
from model_utils import Choices
from model_utils.managers import QueryManager
......@@ -51,6 +52,8 @@ from .activity import (ActivityInProgressError, InstanceActivity)
from .common import BaseResourceConfigModel, Lease
from .network import Interface
from .node import Node, Trait
from network.models import EditorElement
from openstack_auth.user import User
......@@ -247,6 +250,7 @@ class Instance(OperatedMixin, TimeStampedModel):
destroyed_at = DateTimeField(blank=True, null=True,
help_text=_("The virtual machine's time of "
"destruction."))
editor_elements = GenericRelation(EditorElement)
objects = Manager()
active = QueryManager(destroyed_at=None)
......
......@@ -25,6 +25,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from common.models import create_readable
from firewall.models import Vlan, Host
from network.models import Vxlan
from ..tasks import net_tasks
logger = getLogger(__name__)
......@@ -35,9 +36,15 @@ class InterfaceTemplate(Model):
"""Network interface template for an instance template.
If the interface is managed, a host will be created for it.
Use with Vxlan is never managed.
"""
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
vlan = ForeignKey(Vlan, blank=True, null=True,
verbose_name=_('vlan'),
help_text=_('Network the interface belongs to.'))
vxlan = ForeignKey(Vxlan, blank=True, null=True,
verbose_name=_('vxlan'),
help_text=_('Virtual network the interface '
'belongs to.'))
managed = BooleanField(verbose_name=_('managed'), default=True,
help_text=_('If a firewall host (i.e. IP address '
'association) should be generated.'))
......@@ -54,7 +61,10 @@ class InterfaceTemplate(Model):
verbose_name_plural = _('interface templates')
def __unicode__(self):
return "%s - %s - %s" % (self.template, self.vlan, self.managed)
if self.vlan:
return "%s - %s - %s" % (self.template, self.vlan, self.managed)
else: # vxlan
return "%s - %s - %s" % (self.template, self.vxlan, False)
class Interface(Model):
......@@ -64,8 +74,12 @@ class Interface(Model):
MODEL_TYPES = (('virtio', 'virtio'), ('ne2k_pci', 'ne2k_pci'),
('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'),
('e1000', 'e1000'))
vlan = ForeignKey(Vlan, verbose_name=_('vlan'),
vlan = ForeignKey(Vlan, blank=True, null=True,
verbose_name=_('vlan'),
related_name="vm_interface")
vxlan = ForeignKey(Vxlan, blank=True, null=True,
verbose_name=_('vxlan'),
related_name="vm_interface")
host = ForeignKey(Host, verbose_name=_('host'), blank=True, null=True)
instance = ForeignKey('Instance', verbose_name=_('instance'),
related_name='interface_set')
......@@ -77,50 +91,66 @@ class Interface(Model):
ordering = ("-vlan__managed", )
def __unicode__(self):
return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid)
if self.vxlan is None:
return 'cloud-%s-%s' % (str(self.instance.id),
str(self.vlan.vid))
else: # vxlan
return 'cloudx-%s-%s' % (str(self.instance.id),
str(self.vxlan.vni))
@property
def mac(self):
try:
return self.host.mac
except:
return Interface.generate_mac(self.instance, self.vlan)
return Interface.generate_mac(
self.instance,
self.vlan.vid if self.vxlan is None else self.vxlan.vni,
self.vxlan is not None
)
@classmethod
def generate_mac(cls, instance, vlan):
def generate_mac(cls, instance, vid, is_vxlan):
"""Generate MAC address for a VM instance on a VLAN.
"""
# MAC 02:XX:XX:XX:XX:XX
# \________/\__/
# VM ID VLAN ID
# \______/ |\__/
# VM ID | V(X)LAN ID
# __________|_____
# / \
# VXLAN: 1, VLAN: 0
class mac_custom(mac_unix):
word_fmt = '%.2X'
i = instance.id & 0xfffffff
v = vlan.vid & 0xfff
m = (0x02 << 40) | (i << 12) | v
i = instance.id & 0xffffff
v = vid & 0xfff
vx = 1 if is_vxlan else 0
m = (0x02 << 40) | (i << 16) | (vx << 12) | v
return EUI(m, dialect=mac_custom)
def get_vmnetwork_desc(self):
return {
'name': self.__unicode__(),
'bridge': 'cloud',
'bridge': ('cloud' if self.vxlan is None
else 'cloudx-%s' % self.vxlan.vni),
'mac': str(self.mac),
'ipv4': str(self.host.ipv4) if self.host is not None else None,
'ipv6': str(self.host.ipv6) if self.host is not None else None,
'vlan': self.vlan.vid,
'vxlan': self.vxlan.vni if self.vxlan is not None else None,
'model': self.model,
'managed': self.host is not None
}
@classmethod
def create(cls, instance, vlan, managed, owner=None, base_activity=None):
def create(cls, instance, vlan, managed, vxlan=None,
owner=None, base_activity=None):
"""Create a new interface for a VM instance to the specified VLAN.
"""
if managed:
if managed and vxlan is None:
host = Host()
host.vlan = vlan
# TODO change Host's mac field's type to EUI in firewall
host.mac = str(cls.generate_mac(instance, vlan))
host.mac = str(cls.generate_mac(instance, vlan.vid, False))
host.hostname = instance.vm_name
# Get addresses from firewall
if base_activity is None:
......@@ -159,7 +189,7 @@ class Interface(Model):
else:
host = None
iface = cls(vlan=vlan, host=host, instance=instance)
iface = cls(vlan=vlan, vxlan=vxlan, host=host, instance=instance)
iface.save()
return iface
......@@ -180,7 +210,10 @@ class Interface(Model):
def save_as_template(self, instance_template):
"""Create a template based on this interface.
"""
i = InterfaceTemplate(vlan=self.vlan, managed=self.host is not None,
i = InterfaceTemplate(vlan=self.vlan,
managed=(
self.host is not None or
self.vxlan or not None),
template=instance_template)
i.save()
return i
......@@ -189,6 +189,8 @@ class AddInterfaceOperation(InstanceOperation):
"the VM.")
required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'ACTIVE')
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
network_type = None
def rollback(self, net, activity):
with activity.sub_activity(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment