Commit 23530b0b by Szabolcs Gelencser

Merge usernetworks

parents 132dd4a5 cc5b0edb
...@@ -2,24 +2,51 @@ ...@@ -2,24 +2,51 @@
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="1fbec8af-5a7c-40f9-b994-83ac07d1ae1d" name="Default" comment=""> <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$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
<change beforePath="$PROJECT_DIR$/circle/acl/__init__.py" afterPath="" /> <change beforePath="$PROJECT_DIR$/circle/bower.json" afterPath="$PROJECT_DIR$/circle/bower.json" />
<change beforePath="$PROJECT_DIR$/circle/acl/management/__init__.py" afterPath="" /> <change beforePath="$PROJECT_DIR$/circle/circle/db.sqlite3" afterPath="$PROJECT_DIR$/circle/circle/db.sqlite3" />
<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/circle/settings/base.py" afterPath="$PROJECT_DIR$/circle/circle/settings/base.py" /> <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/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/dashboard/static/dashboard/activity.js" afterPath="$PROJECT_DIR$/circle/dashboard/static/dashboard/activity.js" />
<change beforePath="$PROJECT_DIR$/circle/firewall/models.py" afterPath="$PROJECT_DIR$/circle/firewall/models.py" /> <change beforePath="$PROJECT_DIR$/circle/dashboard/static/dashboard/dashboard.js" afterPath="$PROJECT_DIR$/circle/dashboard/static/dashboard/dashboard.js" />
<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/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/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> </list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" /> <option name="TRACKING_ENABLED" value="true" />
...@@ -44,66 +71,7 @@ ...@@ -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)" /> <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>
<component name="FileEditorManager"> <component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300"> <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>
</component> </component>
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
...@@ -115,11 +83,6 @@ ...@@ -115,11 +83,6 @@
</component> </component>
<component name="FindInProjectRecents"> <component name="FindInProjectRecents">
<findStrings> <findStrings>
<find>instance</find>
<find>Instance</find>
<find>Instance(</find>
<find>get_ob</find>
<find>check_auth</find>
<find>installed</find> <find>installed</find>
<find>openstack_auth</find> <find>openstack_auth</find>
<find>vm_ops</find> <find>vm_ops</find>
...@@ -144,7 +107,12 @@ ...@@ -144,7 +107,12 @@
<find>destroyope</find> <find>destroyope</find>
<find>ACL_LEVELS</find> <find>ACL_LEVELS</find>
<find>a</find> <find>a</find>
<find>get_env_variable</find>
<find>editor</find>
<find>network</find>
<find>acl</find> <find>acl</find>
<find>check</find>
<find>checked</find>
</findStrings> </findStrings>
<replaceStrings> <replaceStrings>
<replace>'ACTIVE'</replace> <replace>'ACTIVE'</replace>
...@@ -156,7 +124,6 @@ ...@@ -156,7 +124,6 @@
<component name="IdeDocumentHistory"> <component name="IdeDocumentHistory">
<option name="CHANGED_PATHS"> <option name="CHANGED_PATHS">
<list> <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/vm/models/__init__.py" />
<option value="$PROJECT_DIR$/circle/dashboard/admin.py" /> <option value="$PROJECT_DIR$/circle/dashboard/admin.py" />
<option value="$PROJECT_DIR$/circle/request/models.py" /> <option value="$PROJECT_DIR$/circle/request/models.py" />
...@@ -178,11 +145,9 @@ ...@@ -178,11 +145,9 @@
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/index-vm.html" /> <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/base.html" />
<option value="$PROJECT_DIR$/circle/dashboard/templates/dashboard/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/dashboard/forms.py" />
<option value="$PROJECT_DIR$/circle/vm/managers/os_instance_manager.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/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/templates/dashboard/vm-detail/access.html" />
<option value="$PROJECT_DIR$/circle/dashboard/views/__init__.py" /> <option value="$PROJECT_DIR$/circle/dashboard/views/__init__.py" />
<option value="$PROJECT_DIR$/circle/dashboard/views/index.py" /> <option value="$PROJECT_DIR$/circle/dashboard/views/index.py" />
...@@ -197,15 +162,19 @@ ...@@ -197,15 +162,19 @@
<option value="$PROJECT_DIR$/circle/circle/os_policies/keystone_policy.json" /> <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/openstack_auth/__init__.py" />
<option value="$PROJECT_DIR$/circle/circle/os_policies/nova_policy.json" /> <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/dashboard/views/util.py" />
<option value="$PROJECT_DIR$/circle/common/operations.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/common.py" />
<option value="$PROJECT_DIR$/circle/vm/models/instance.py" /> <option value="$PROJECT_DIR$/circle/vm/models/instance.py" />
<option value="$PROJECT_DIR$/circle/firewall/models.py" /> <option value="$PROJECT_DIR$/circle/firewall/models.py" />
<option value="$PROJECT_DIR$/circle/dashboard/models.py" /> <option value="$PROJECT_DIR$/circle/dashboard/models.py" />
<option value="$PROJECT_DIR$/circle/dashboard/views/vm.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> </list>
</option> </option>
</component> </component>
...@@ -275,40 +244,13 @@ ...@@ -275,40 +244,13 @@
<item name="cloud" type="b2602c69:ProjectViewProjectNode" /> <item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" /> <item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" /> <item name="circle" type="462c0819:PsiDirectoryNode" />
<item name="circle" 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="os_policies" type="462c0819:PsiDirectoryNode" />
</path> </path>
<path> <path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" /> <item name="cloud" type="b2602c69:ProjectViewProjectNode" />
<item name="cloud" type="462c0819:PsiDirectoryNode" /> <item name="cloud" type="462c0819:PsiDirectoryNode" />
<item name="circle" type="462c0819:PsiDirectoryNode" /> <item name="circle" 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>
<path> <path>
<item name="cloud" type="b2602c69:ProjectViewProjectNode" /> <item name="cloud" type="b2602c69:ProjectViewProjectNode" />
...@@ -332,7 +274,7 @@ ...@@ -332,7 +274,7 @@
<property name="settings.editor.selected.configurable" value="preferences.keymap" /> <property name="settings.editor.selected.configurable" value="preferences.keymap" />
<property name="NewWatcherDialog.advanced.open" value="true" /> <property name="NewWatcherDialog.advanced.open" value="true" />
<property name="DefaultHtmlFileTemplate" value="HTML File" /> <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>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
...@@ -394,21 +336,20 @@ ...@@ -394,21 +336,20 @@
</component> </component>
<component name="ToolWindowManager"> <component name="ToolWindowManager">
<frame x="-2" y="-1" width="1924" height="1063" extended-state="0" /> <frame x="-2" y="-1" width="1924" height="1063" extended-state="0" />
<editor active="true" />
<layout> <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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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" /> <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 @@ ...@@ -571,50 +512,6 @@
</expressions> </expressions>
</component> </component>
<component name="editorHistoryManager"> <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"> <entry file="file://$PROJECT_DIR$/circle/dashboard/templates/dashboard/operate.html">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0"> <state relative-caret-position="0">
...@@ -693,6 +590,7 @@ ...@@ -693,6 +590,7 @@
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="190"> <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" /> <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> </state>
</provider> </provider>
</entry> </entry>
...@@ -746,13 +644,6 @@ ...@@ -746,13 +644,6 @@
</state> </state>
</provider> </provider>
</entry> </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"> <entry file="file://$PROJECT_DIR$/circle/common/models.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="185"> <state relative-caret-position="185">
...@@ -777,13 +668,6 @@ ...@@ -777,13 +668,6 @@
</state> </state>
</provider> </provider>
</entry> </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"> <entry file="file:///usr/lib/python2.7/importlib/__init__.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="377"> <state relative-caret-position="377">
...@@ -861,16 +745,6 @@ ...@@ -861,16 +745,6 @@
</state> </state>
</provider> </provider>
</entry> </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"> <entry file="file://$PROJECT_DIR$/circle/dashboard/views/util.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="4437"> <state relative-caret-position="4437">
...@@ -903,14 +777,6 @@ ...@@ -903,14 +777,6 @@
</state> </state>
</provider> </provider>
</entry> </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"> <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"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="306"> <state relative-caret-position="306">
...@@ -922,9 +788,9 @@ ...@@ -922,9 +788,9 @@
<entry file="file://$PROJECT_DIR$/circle/vm/models/common.py"> <entry file="file://$PROJECT_DIR$/circle/vm/models/common.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-816"> <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> <folding>
<element signature="e#732#788#0" expanded="true" /> <element signature="e#732#788#0" expanded="false" />
</folding> </folding>
</state> </state>
</provider> </provider>
...@@ -934,7 +800,7 @@ ...@@ -934,7 +800,7 @@
<state relative-caret-position="182"> <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" /> <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> <folding>
<element signature="e#732#788#0" expanded="true" /> <element signature="e#732#788#0" expanded="false" />
</folding> </folding>
</state> </state>
</provider> </provider>
...@@ -944,25 +810,115 @@ ...@@ -944,25 +810,115 @@
<state relative-caret-position="182"> <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" /> <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> <folding>
<element signature="e#757#789#0" expanded="true" /> <element signature="e#757#789#0" expanded="false" />
</folding> </folding>
</state> </state>
</provider> </provider>
</entry> </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"> <entry file="file://$PROJECT_DIR$/circle/dashboard/models.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="182"> <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" /> <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> <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" /> <element signature="e#732#770#0" expanded="true" />
</folding> </folding>
</state> </state>
</provider> </provider>
</entry> </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"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="476"> <state relative-caret-position="85">
<caret line="141" column="39" lean-forward="true" selection-start-line="141" selection-start-column="39" selection-end-line="141" selection-end-column="39" /> <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 /> <folding />
</state> </state>
</provider> </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 @@ ...@@ -22,6 +22,7 @@
"favico.js": "~0.3.5", "favico.js": "~0.3.5",
"datatables": "~1.10.4", "datatables": "~1.10.4",
"chart.js": "2.3.0", "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 = { ...@@ -548,4 +548,8 @@ POLICY_FILES = {
POLICY_DIRS = { POLICY_DIRS = {
'compute': ['nova_policy.d'], 'compute': ['nova_policy.d'],
'volume': ['cinder_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: ...@@ -110,6 +110,7 @@ if DEBUG:
PIPELINE["COMPILERS"] = ( PIPELINE["COMPILERS"] = (
'dashboard.compilers.DummyLessCompiler', 'dashboard.compilers.DummyLessCompiler',
'pipeline.compilers.es6.ES6Compiler',
) )
ADMIN_ENABLED = True 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() ...@@ -30,7 +30,7 @@ utils.patch_middleware_get_user()
urlpatterns = [ urlpatterns = [
url(r'^$', lambda x: redirect(reverse("dashboard.index"))), 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'^blacklist-add/', add_blacklist_item),
url(r'^dashboard/', include('dashboard.urls')), url(r'^dashboard/', include('dashboard.urls')),
url(r'^request/', include('request.urls')), url(r'^request/', include('request.urls')),
......
...@@ -19,8 +19,11 @@ from sys import exc_info ...@@ -19,8 +19,11 @@ from sys import exc_info
import logging 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.template import RequestContext
from django.http import JsonResponse
from django.utils.translation import ugettext_lazy as _
from .models import HumanReadableException from .models import HumanReadableException
...@@ -59,3 +62,29 @@ def handler403(request): ...@@ -59,3 +62,29 @@ def handler403(request):
resp = render_to_response("403.html", ctx) resp = render_to_response("403.html", ctx)
resp.status_code = 403 resp.status_code = 403
return resp 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): ...@@ -947,16 +947,27 @@ class VmRemoveInterfaceForm(OperationForm):
class VmAddInterfaceForm(OperationForm): class VmAddInterfaceForm(OperationForm):
network_type = 'vlan'
label = _('Vlan')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices') choices = kwargs.pop('choices')
super(VmAddInterfaceForm, self).__init__(*args, **kwargs) super(VmAddInterfaceForm, self).__init__(*args, **kwargs)
field = forms.ModelChoiceField( field = forms.ModelChoiceField(
queryset=choices, required=True, label=_('Vlan')) queryset=choices, required=False,
label=self.label)
if not choices: if not choices:
field.widget.attrs['disabled'] = 'disabled' field.widget.attrs['disabled'] = 'disabled'
field.empty_label = _('No more networks.') 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): class DeployChoiceField(forms.ModelChoiceField):
...@@ -1298,6 +1309,9 @@ class UserEditForm(forms.ModelForm): ...@@ -1298,6 +1309,9 @@ class UserEditForm(forms.ModelForm):
instance_limit = forms.IntegerField( instance_limit = forms.IntegerField(
label=_('Instance limit'), label=_('Instance limit'),
min_value=0, widget=NumberInput) min_value=0, widget=NumberInput)
network_limit = forms.IntegerField(
label=_('Virtual network limit'),
min_value=0, widget=NumberInput)
two_factor_secret = forms.CharField( two_factor_secret = forms.CharField(
label=_('Two-factor authentication secret'), label=_('Two-factor authentication secret'),
help_text=_("Remove the secret key to disable two-factor " help_text=_("Remove the secret key to disable two-factor "
...@@ -1307,18 +1321,22 @@ class UserEditForm(forms.ModelForm): ...@@ -1307,18 +1321,22 @@ class UserEditForm(forms.ModelForm):
super(UserEditForm, self).__init__(*args, **kwargs) super(UserEditForm, self).__init__(*args, **kwargs)
self.fields["instance_limit"].initial = ( self.fields["instance_limit"].initial = (
self.instance.profile.instance_limit) self.instance.profile.instance_limit)
self.fields["network_limit"].initial = (
self.instance.profile.network_limit)
self.fields["two_factor_secret"].initial = ( self.fields["two_factor_secret"].initial = (
self.instance.profile.two_factor_secret) self.instance.profile.two_factor_secret)
class Meta: class Meta:
model = User model = User
fields = ('email', 'first_name', 'last_name', 'instance_limit', 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): def save(self, commit=True):
user = super(UserEditForm, self).save() user = super(UserEditForm, self).save()
user.profile.instance_limit = ( user.profile.instance_limit = (
self.cleaned_data['instance_limit'] or None) self.cleaned_data['instance_limit'] or None)
user.profile.network_limit = (
self.cleaned_data['network_limit'] or None)
user.profile.two_factor_secret = ( user.profile.two_factor_secret = (
self.cleaned_data['two_factor_secret'] or None) self.cleaned_data['two_factor_secret'] or None)
user.profile.save() user.profile.save()
......
...@@ -125,6 +125,11 @@ class Command(BaseCommand): ...@@ -125,6 +125,11 @@ class Command(BaseCommand):
vm.snat_to.add(net) vm.snat_to.add(net)
vm.snat_to.add(vm) 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 # default vlan groups
vg_all = self.create(VlanGroup, 'name', name='all') vg_all = self.create(VlanGroup, 'name', name='all')
vg_all.vlans.add(vm, man, net) 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): ...@@ -178,6 +178,8 @@ class Profile(Model):
unique=True, blank=True, null=True, max_length=64, unique=True, blank=True, null=True, max_length=64,
help_text=_('Unique identifier of the person, e.g. a student number.')) help_text=_('Unique identifier of the person, e.g. a student number.'))
instance_limit = IntegerField(default=5) instance_limit = IntegerField(default=5)
network_limit = IntegerField(default=2,
verbose_name="Virtual network limit")
use_gravatar = BooleanField( use_gravatar = BooleanField(
verbose_name=_("Use Gravatar"), default=True, verbose_name=_("Use Gravatar"), default=True,
help_text=_("Whether to use email address as Gravatar profile image")) help_text=_("Whether to use email address as Gravatar profile image"))
......
...@@ -28,7 +28,7 @@ $(function() { ...@@ -28,7 +28,7 @@ $(function() {
}); });
/* operations */ /* 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'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
$.ajax({ $.ajax({
......
...@@ -223,6 +223,7 @@ $(function () { ...@@ -223,6 +223,7 @@ $(function () {
register_search($("#dashboard-group-search-form"), $("#dashboard-group-list"), generateGroupHTML); register_search($("#dashboard-group-search-form"), $("#dashboard-group-list"), generateGroupHTML);
register_search($("#dashboard-user-search-form"), $("#dashboard-user-list"), generateUserHTML); register_search($("#dashboard-user-search-form"), $("#dashboard-user-list"), generateUserHTML);
register_search($("#dashboard-template-search-form"), $("#dashboard-template-list"), generateTemplateHTML); register_search($("#dashboard-template-search-form"), $("#dashboard-template-list"), generateTemplateHTML);
register_search($("#dashboard-vxlan-search-form"), $("#dashboard-vxlan-list"), generateVxlanHTML);
/* notification message toggle */ /* notification message toggle */
$(document).on('click', ".notification-message-subject", function() { $(document).on('click', ".notification-message-subject", function() {
...@@ -331,6 +332,18 @@ function generateNodeHTML(data, is_last) { ...@@ -331,6 +332,18 @@ function generateNodeHTML(data, is_last) {
'</a>'; '</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 */ /* copare vm-s by fav, pk order */
function compareVmByFav(a, b) { function compareVmByFav(a, b) {
if(a.fav && b.fav) { if(a.fav && b.fav) {
......
...@@ -584,7 +584,8 @@ footer a, footer a:hover, footer a:visited { ...@@ -584,7 +584,8 @@ footer a, footer a:hover, footer a:visited {
} }
#dashboard-vm-list, #dashboard-node-list, #dashboard-group-list, #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; 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 @@ ...@@ -48,6 +48,11 @@
{# {% include "dashboard/index-users.html" %}#} {# {% include "dashboard/index-users.html" %}#}
{# </div>#} {# </div>#}
{# {% endif %}#} {# {% endif %}#}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vxlans.html" %}
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
...@@ -8,6 +8,15 @@ ...@@ -8,6 +8,15 @@
<i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a> <i class="fa fa-{{op.icon}}"></i> {% trans "add interface" %}</a>
{% endif %}{% endwith %} {% endif %}{% endwith %}
</div> </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> <h2>
{% trans "Interfaces" %} {% trans "Interfaces" %}
</h2> </h2>
...@@ -16,12 +25,28 @@ ...@@ -16,12 +25,28 @@
{% for i in instance.interface_set.all %} {% for i in instance.interface_set.all %}
<div> <div>
<h3 class="list-group-item-heading dashboard-vm-details-network-h3"> <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 }} <i class="fa fa-{% if i.host %}globe{% else %}link{% endif %}"></i>
{% if not i.host%}({% trans "unmanaged" %}){% endif %} {% 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 %} {% if user.is_superuser and i.host %}
<a href="{{ i.host.get_absolute_url }}" <a href="{{ i.host.get_absolute_url }}"
class="btn btn-default btn-xs">{% trans "edit" %}</a> class="btn btn-default btn-xs">{% trans "edit" %}</a>
{% endif %} {% 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 %} {% with op=op.remove_interface %}{% if op %}
<span class="operation-wrapper"> <span class="operation-wrapper">
<a href="{{op.get_url}}?interface={{ i.pk }}" <a href="{{op.get_url}}?interface={{ i.pk }}"
...@@ -30,6 +55,7 @@ ...@@ -30,6 +55,7 @@
</a> </a>
</span> </span>
{% endif %}{% endwith %} {% endif %}{% endwith %}
{% endif %}
</h3> </h3>
{% if i.host %} {% if i.host %}
<div class="row"> <div class="row">
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
<!--<i class="fa fa-refresh fa-spin fa-2x"></i>--> <!--<i class="fa fa-refresh fa-spin fa-2x"></i>-->
</div> </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 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> <h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Virtual machines" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
......
...@@ -25,6 +25,8 @@ from django.contrib.auth.models import Group, User ...@@ -25,6 +25,8 @@ from django.contrib.auth.models import Group, User
from django.core.cache import cache from django.core.cache import cache
from django.views.generic import TemplateView from django.views.generic import TemplateView
from vm.models import Instance, Node, InstanceTemplate from vm.models import Instance, Node, InstanceTemplate
from dashboard.views.vm import vm_ops
from network.models import Vxlan
from ..store_api import Store from ..store_api import Store
......
...@@ -20,13 +20,14 @@ from django.core.urlresolvers import reverse_lazy ...@@ -20,13 +20,14 @@ from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper 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 crispy_forms.bootstrap import FormActions, FieldWithButtons, StrictButton
from firewall.models import ( from firewall.models import (
Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup, Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
SwitchPort, Firewall SwitchPort, Firewall
) )
from network.models import Vxlan
class LinkButton(BaseInput): class LinkButton(BaseInput):
...@@ -348,3 +349,53 @@ class VlanGroupForm(ModelForm): ...@@ -348,3 +349,53 @@ class VlanGroupForm(ModelForm):
class Meta: class Meta:
model = VlanGroup model = VlanGroup
fields = ("name", "vlans", "description", "owner", ) 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 @@ ...@@ -15,4 +15,103 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # 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, ...@@ -24,6 +24,8 @@ from django_tables2.columns import (LinkColumn, TemplateColumn, Column,
BooleanColumn) BooleanColumn)
from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort from firewall.models import Host, Vlan, Domain, Group, Record, Rule, SwitchPort
from network.models import Vxlan
from vm.models import Interface
class MACColumn(Column): class MACColumn(Column):
...@@ -233,6 +235,16 @@ class VlanGroupTable(Table): ...@@ -233,6 +235,16 @@ class VlanGroupTable(Table):
order_by = 'name' 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): class HostRecordsTable(Table):
fqdn = LinkColumn( fqdn = LinkColumn(
"network.record", args=[A("pk")], "network.record", args=[A("pk")],
...@@ -282,3 +294,16 @@ class FirewallRuleTable(Table): ...@@ -282,3 +294,16 @@ class FirewallRuleTable(Table):
'action', 'proto', 'actions') 'action', 'proto', 'actions')
order_by = '-pk' order_by = '-pk'
empty_text = _("No related rules found.") 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 @@ ...@@ -29,7 +29,22 @@
</div> </div>
{% trans "Vlans" %} {% trans "Vlans" %}
<div> <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> </div>
</h3> </h3>
...@@ -44,7 +59,7 @@ ...@@ -44,7 +59,7 @@
</div> </div>
{% trans "Domains" %} {% trans "Domains" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "Domain names" %}</small>
</div> </div>
</h3> </h3>
...@@ -59,7 +74,7 @@ ...@@ -59,7 +74,7 @@
</div> </div>
{% trans "Records" %} {% trans "Records" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "DNS records" %}</small>
</div> </div>
</h3> </h3>
...@@ -74,7 +89,7 @@ ...@@ -74,7 +89,7 @@
</div> </div>
{% trans "Blacklist items" %} {% trans "Blacklist items" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "List of denied IPs and exceptions" %}</small>
</div> </div>
</h3> </h3>
...@@ -89,7 +104,7 @@ ...@@ -89,7 +104,7 @@
</div> </div>
{% trans "Rules" %} {% trans "Rules" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "Firewall rules" %}</small>
</div> </div>
</h3> </h3>
...@@ -104,7 +119,7 @@ ...@@ -104,7 +119,7 @@
</div> </div>
{% trans "Switch ports" %} {% trans "Switch ports" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "Switch ports" %}</small>
</div> </div>
</h3> </h3>
...@@ -119,7 +134,7 @@ ...@@ -119,7 +134,7 @@
</div> </div>
{% trans "Vlan groups" %} {% trans "Vlan groups" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "Groups of 802.1Q virtual networks" %}</small>
</div> </div>
</h3> </h3>
...@@ -134,7 +149,7 @@ ...@@ -134,7 +149,7 @@
</div> </div>
{% trans "Host groups" %} {% trans "Host groups" %}
<div> <div>
<small>{% trans "Hosts are machines on the network" %}</small> <small>{% trans "Groups of hosts are machines on the network" %}</small>
</div> </div>
</h3> </h3>
</div> </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 ( ...@@ -20,6 +20,7 @@ from .views import (
IndexView, IndexView,
HostList, HostDetail, HostCreate, HostDelete, HostList, HostDetail, HostCreate, HostDelete,
VlanList, VlanDetail, VlanDelete, VlanCreate, VlanList, VlanDetail, VlanDelete, VlanCreate,
VxlanList, VxlanDetail, VxlanDelete, VxlanCreate, VxlanAclUpdateView,
DomainList, DomainDetail, DomainDelete, DomainCreate, DomainList, DomainDetail, DomainDelete, DomainCreate,
GroupList, GroupDetail, GroupDelete, GroupCreate, GroupList, GroupDetail, GroupDelete, GroupCreate,
RecordList, RecordDetail, RecordCreate, RecordDelete, RecordList, RecordDetail, RecordCreate, RecordDelete,
...@@ -30,7 +31,7 @@ from .views import ( ...@@ -30,7 +31,7 @@ from .views import (
FirewallList, FirewallDetail, FirewallCreate, FirewallDelete, FirewallList, FirewallDetail, FirewallCreate, FirewallDelete,
remove_host_group, add_host_group, remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device, remove_switch_port_device, add_switch_port_device,
VlanAclUpdateView VlanAclUpdateView, NetworkEditorView
) )
urlpatterns = [ urlpatterns = [
...@@ -125,6 +126,19 @@ urlpatterns = [ ...@@ -125,6 +126,19 @@ urlpatterns = [
url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(), url('^rules/delete/(?P<pk>\d+)/$', RuleDelete.as_view(),
name="network.rule_delete"), 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 # non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group, url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
name='network.remove_host_group'), name='network.remove_host_group'),
......
...@@ -15,32 +15,43 @@ ...@@ -15,32 +15,43 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import logging
import random
import json
from collections import OrderedDict from collections import OrderedDict
from netaddr import IPNetwork from netaddr import IPNetwork
from django.views.generic import (TemplateView, UpdateView, DeleteView, from django.views.generic import (
CreateView) TemplateView, UpdateView, DeleteView, CreateView,
from django.core.exceptions import ValidationError )
from django.core.exceptions import (
ValidationError, PermissionDenied, ImproperlyConfigured
)
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.db.models import Q from django.db.models import Q
from django.conf import settings
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from firewall.models import ( from firewall.models import (
Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup, Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
SwitchPort, EthernetDevice, Firewall) SwitchPort, EthernetDevice, Firewall
from vm.models import Interface )
from network.models import Vxlan, EditorElement
from vm.models import Interface, Instance
from common.views import CreateLimitedResourceMixin
from .tables import ( from .tables import (
HostTable, VlanTable, SmallHostTable, DomainTable, GroupTable, HostTable, VlanTable, SmallHostTable, DomainTable, GroupTable,
RecordTable, BlacklistItemTable, RuleTable, VlanGroupTable, RecordTable, BlacklistItemTable, RuleTable, VlanGroupTable,
SmallRuleTable, SmallGroupRuleTable, SmallRecordTable, SwitchPortTable, SmallRuleTable, SmallGroupRuleTable, SmallRecordTable, SwitchPortTable,
SmallDhcpTable, FirewallTable, FirewallRuleTable, SmallDhcpTable, FirewallTable, FirewallRuleTable, VxlanTable, SmallVmTable,
) )
from .forms import ( from .forms import (
HostForm, VlanForm, DomainForm, GroupForm, RecordForm, BlacklistItemForm, HostForm, VlanForm, DomainForm, GroupForm, RecordForm, BlacklistItemForm,
RuleForm, VlanGroupForm, SwitchPortForm, FirewallForm RuleForm, VlanGroupForm, SwitchPortForm, FirewallForm,
VxlanForm, VxlanSuperUserForm,
) )
from django.contrib import messages from django.contrib import messages
...@@ -48,7 +59,6 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -48,7 +59,6 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
# from django.db.models import Q
from operator import itemgetter from operator import itemgetter
from itertools import chain from itertools import chain
from dashboard.views import AclUpdateView from dashboard.views import AclUpdateView
...@@ -72,6 +82,9 @@ except ImportError: ...@@ -72,6 +82,9 @@ except ImportError:
content_type=content_type) content_type=content_type)
logger = logging.getLogger(__name__)
class MagicMixin(object): class MagicMixin(object):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
...@@ -508,7 +521,7 @@ class HostDetail(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -508,7 +521,7 @@ class HostDetail(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
class HostCreate(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin, class HostCreate(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView): SuccessMessageMixin, CreateView):
model = Host model = Host
template_name = "network/host-create.html" template_name = "network/host-create.html"
form_class = HostForm form_class = HostForm
...@@ -802,6 +815,7 @@ class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -802,6 +815,7 @@ class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
slug_field = 'vid' slug_field = 'vid'
slug_url_kwarg = 'vid' slug_url_kwarg = 'vid'
success_message = _(u'Succesfully modified vlan %(name)s.') success_message = _(u'Succesfully modified vlan %(name)s.')
success_url = reverse_lazy('network.vlan_list')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(VlanDetail, self).get_context_data(**kwargs) context = super(VlanDetail, self).get_context_data(**kwargs)
...@@ -813,8 +827,6 @@ class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -813,8 +827,6 @@ class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
context['aclform'] = AclUserOrGroupAddForm() context['aclform'] = AclUserOrGroupAddForm()
return context return context
success_url = reverse_lazy('network.vlan_list')
class VlanCreate(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin, class VlanCreate(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, InitialOwnerMixin, CreateView): SuccessMessageMixin, InitialOwnerMixin, CreateView):
...@@ -916,6 +928,370 @@ class VlanGroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): ...@@ -916,6 +928,370 @@ class VlanGroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
return reverse_lazy('network.vlan_group_list') 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): def remove_host_group(request, **kwargs):
host = Host.objects.get(pk=kwargs['pk']) host = Host.objects.get(pk=kwargs['pk'])
group = Group.objects.get(pk=kwargs['group_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 ...@@ -34,6 +34,7 @@ from django.db import IntegrityError
from django.dispatch import Signal from django.dispatch import Signal
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop 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 import Choices
from model_utils.managers import QueryManager from model_utils.managers import QueryManager
...@@ -51,6 +52,8 @@ from .activity import (ActivityInProgressError, InstanceActivity) ...@@ -51,6 +52,8 @@ from .activity import (ActivityInProgressError, InstanceActivity)
from .common import BaseResourceConfigModel, Lease from .common import BaseResourceConfigModel, Lease
from .network import Interface from .network import Interface
from .node import Node, Trait from .node import Node, Trait
from network.models import EditorElement
from openstack_auth.user import User from openstack_auth.user import User
...@@ -247,6 +250,7 @@ class Instance(OperatedMixin, TimeStampedModel): ...@@ -247,6 +250,7 @@ class Instance(OperatedMixin, TimeStampedModel):
destroyed_at = DateTimeField(blank=True, null=True, destroyed_at = DateTimeField(blank=True, null=True,
help_text=_("The virtual machine's time of " help_text=_("The virtual machine's time of "
"destruction.")) "destruction."))
editor_elements = GenericRelation(EditorElement)
objects = Manager() objects = Manager()
active = QueryManager(destroyed_at=None) active = QueryManager(destroyed_at=None)
......
...@@ -25,6 +25,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop ...@@ -25,6 +25,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from common.models import create_readable from common.models import create_readable
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from network.models import Vxlan
from ..tasks import net_tasks from ..tasks import net_tasks
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -35,9 +36,15 @@ class InterfaceTemplate(Model): ...@@ -35,9 +36,15 @@ class InterfaceTemplate(Model):
"""Network interface template for an instance template. """Network interface template for an instance template.
If the interface is managed, a host will be created for it. 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.')) 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, managed = BooleanField(verbose_name=_('managed'), default=True,
help_text=_('If a firewall host (i.e. IP address ' help_text=_('If a firewall host (i.e. IP address '
'association) should be generated.')) 'association) should be generated.'))
...@@ -54,7 +61,10 @@ class InterfaceTemplate(Model): ...@@ -54,7 +61,10 @@ class InterfaceTemplate(Model):
verbose_name_plural = _('interface templates') verbose_name_plural = _('interface templates')
def __unicode__(self): 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): class Interface(Model):
...@@ -64,8 +74,12 @@ class Interface(Model): ...@@ -64,8 +74,12 @@ class Interface(Model):
MODEL_TYPES = (('virtio', 'virtio'), ('ne2k_pci', 'ne2k_pci'), MODEL_TYPES = (('virtio', 'virtio'), ('ne2k_pci', 'ne2k_pci'),
('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'), ('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'),
('e1000', 'e1000')) ('e1000', 'e1000'))
vlan = ForeignKey(Vlan, verbose_name=_('vlan'), vlan = ForeignKey(Vlan, blank=True, null=True,
verbose_name=_('vlan'),
related_name="vm_interface") 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) host = ForeignKey(Host, verbose_name=_('host'), blank=True, null=True)
instance = ForeignKey('Instance', verbose_name=_('instance'), instance = ForeignKey('Instance', verbose_name=_('instance'),
related_name='interface_set') related_name='interface_set')
...@@ -77,50 +91,66 @@ class Interface(Model): ...@@ -77,50 +91,66 @@ class Interface(Model):
ordering = ("-vlan__managed", ) ordering = ("-vlan__managed", )
def __unicode__(self): 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 @property
def mac(self): def mac(self):
try: try:
return self.host.mac return self.host.mac
except: 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 @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. """Generate MAC address for a VM instance on a VLAN.
""" """
# MAC 02:XX:XX:XX:XX:XX # 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): class mac_custom(mac_unix):
word_fmt = '%.2X' word_fmt = '%.2X'
i = instance.id & 0xfffffff i = instance.id & 0xffffff
v = vlan.vid & 0xfff v = vid & 0xfff
m = (0x02 << 40) | (i << 12) | v vx = 1 if is_vxlan else 0
m = (0x02 << 40) | (i << 16) | (vx << 12) | v
return EUI(m, dialect=mac_custom) return EUI(m, dialect=mac_custom)
def get_vmnetwork_desc(self): def get_vmnetwork_desc(self):
return { return {
'name': self.__unicode__(), 'name': self.__unicode__(),
'bridge': 'cloud', 'bridge': ('cloud' if self.vxlan is None
else 'cloudx-%s' % self.vxlan.vni),
'mac': str(self.mac), 'mac': str(self.mac),
'ipv4': str(self.host.ipv4) if self.host is not None else None, '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, 'ipv6': str(self.host.ipv6) if self.host is not None else None,
'vlan': self.vlan.vid, 'vlan': self.vlan.vid,
'vxlan': self.vxlan.vni if self.vxlan is not None else None,
'model': self.model, 'model': self.model,
'managed': self.host is not None 'managed': self.host is not None
} }
@classmethod @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. """Create a new interface for a VM instance to the specified VLAN.
""" """
if managed: if managed and vxlan is None:
host = Host() host = Host()
host.vlan = vlan host.vlan = vlan
# TODO change Host's mac field's type to EUI in firewall # 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 host.hostname = instance.vm_name
# Get addresses from firewall # Get addresses from firewall
if base_activity is None: if base_activity is None:
...@@ -159,7 +189,7 @@ class Interface(Model): ...@@ -159,7 +189,7 @@ class Interface(Model):
else: else:
host = None host = None
iface = cls(vlan=vlan, host=host, instance=instance) iface = cls(vlan=vlan, vxlan=vxlan, host=host, instance=instance)
iface.save() iface.save()
return iface return iface
...@@ -180,7 +210,10 @@ class Interface(Model): ...@@ -180,7 +210,10 @@ class Interface(Model):
def save_as_template(self, instance_template): def save_as_template(self, instance_template):
"""Create a template based on this interface. """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) template=instance_template)
i.save() i.save()
return i return i
...@@ -189,6 +189,8 @@ class AddInterfaceOperation(InstanceOperation): ...@@ -189,6 +189,8 @@ class AddInterfaceOperation(InstanceOperation):
"the VM.") "the VM.")
required_perms = () required_perms = ()
accept_states = ('STOPPED', 'PENDING', 'ACTIVE') accept_states = ('STOPPED', 'PENDING', 'ACTIVE')
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
network_type = None
def rollback(self, net, activity): def rollback(self, net, activity):
with activity.sub_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