From 5a4b802dfc2646136ed8c0f371ace699bd1d3c84 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sun, 20 May 2018 20:23:53 -0700 Subject: [PATCH] StarlingX open source release updates Signed-off-by: Dean Troyer --- .gitignore | 47 + CONTRIBUTING.rst | 18 + HACKING.rst | 15 + LICENSE | 176 ++ cgcs_dashboard/__init__.py | 0 cgcs_dashboard/api/__init__.py | 31 + cgcs_dashboard/api/ceilometer.py | 52 + cgcs_dashboard/api/ceph.py | 187 ++ cgcs_dashboard/api/dc_manager.py | 73 + cgcs_dashboard/api/iservice.py | 61 + cgcs_dashboard/api/patch.py | 239 ++ cgcs_dashboard/api/rest/__init__.py | 31 + cgcs_dashboard/api/rest/dc_manager.py | 90 + cgcs_dashboard/api/rest/sysinv.py | 38 + cgcs_dashboard/api/sysinv.py | 2601 +++++++++++++++++ cgcs_dashboard/api/vim.py | 129 + cgcs_dashboard/dashboards/__init__.py | 0 cgcs_dashboard/dashboards/admin/__init__.py | 0 .../admin/fault_management/__init__.py | 0 .../admin/fault_management/panel.py | 50 + .../admin/fault_management/tables.py | 284 ++ .../dashboards/admin/fault_management/tabs.py | 318 ++ .../fault_management/_active_alarms.html | 26 + .../fault_management/_detail_history.html | 58 + .../fault_management/_detail_log.html | 50 + .../fault_management/_detail_overview.html | 52 + .../templates/fault_management/_summary.html | 5 + .../templates/fault_management/index.html | 29 + .../dashboards/admin/fault_management/urls.py | 38 + .../admin/fault_management/views.py | 175 ++ .../admin/host_topology/__init__.py | 0 .../dashboards/admin/host_topology/panel.py | 34 + .../dashboards/admin/host_topology/tables.py | 46 + .../dashboards/admin/host_topology/tabs.py | 128 + .../templates/host_topology/_svg_element.html | 261 ++ .../host_topology/detail/_detail_alarms.html | 9 + .../host_topology/detail/providernet.html | 13 + .../host_topology/detail/tabbed_detail.html | 16 + .../templates/host_topology/index.html | 40 + .../dashboards/admin/host_topology/urls.py | 23 + .../dashboards/admin/host_topology/views.py | 235 ++ .../dashboards/admin/inventory/__init__.py | 0 .../admin/inventory/cpu_functions/__init__.py | 0 .../admin/inventory/cpu_functions/forms.py | 400 +++ .../admin/inventory/cpu_functions/tables.py | 81 + .../admin/inventory/cpu_functions/utils.py | 238 ++ .../admin/inventory/cpu_functions/views.py | 193 ++ .../admin/inventory/devices/__init__.py | 0 .../admin/inventory/devices/forms.py | 78 + .../admin/inventory/devices/tables.py | 144 + .../admin/inventory/devices/views.py | 169 ++ .../admin/inventory/interfaces/__init__.py | 0 .../inventory/interfaces/address/__init__.py | 0 .../inventory/interfaces/address/forms.py | 69 + .../inventory/interfaces/address/tables.py | 130 + .../inventory/interfaces/address/views.py | 51 + .../admin/inventory/interfaces/forms.py | 886 ++++++ .../inventory/interfaces/route/__init__.py | 0 .../admin/inventory/interfaces/route/forms.py | 86 + .../inventory/interfaces/route/tables.py | 130 + .../admin/inventory/interfaces/route/views.py | 51 + .../admin/inventory/interfaces/tables.py | 211 ++ .../admin/inventory/interfaces/tabs.py | 41 + .../admin/inventory/interfaces/views.py | 397 +++ .../admin/inventory/lldp/__init__.py | 0 .../dashboards/admin/inventory/lldp/tables.py | 46 + .../dashboards/admin/inventory/lldp/views.py | 101 + .../admin/inventory/memorys/__init__.py | 0 .../admin/inventory/memorys/forms.py | 363 +++ .../admin/inventory/memorys/tables.py | 99 + .../admin/inventory/memorys/views.py | 117 + .../dashboards/admin/inventory/panel.py | 37 + .../admin/inventory/ports/__init__.py | 0 .../dashboards/admin/inventory/ports/forms.py | 123 + .../admin/inventory/ports/tables.py | 89 + .../dashboards/admin/inventory/ports/tabs.py | 41 + .../dashboards/admin/inventory/ports/views.py | 128 + .../admin/inventory/sensors/__init__.py | 0 .../admin/inventory/sensors/forms.py | 235 ++ .../admin/inventory/sensors/tables.py | 401 +++ .../admin/inventory/sensors/views.py | 218 ++ .../admin/inventory/storages/__init__.py | 0 .../admin/inventory/storages/forms.py | 816 ++++++ .../inventory/storages/lvg_params/__init__.py | 0 .../inventory/storages/lvg_params/forms.py | 354 +++ .../inventory/storages/lvg_params/tables.py | 67 + .../inventory/storages/lvg_params/urls.py | 17 + .../inventory/storages/lvg_params/views.py | 56 + .../admin/inventory/storages/tables.py | 631 ++++ .../admin/inventory/storages/tabs.py | 64 + .../admin/inventory/storages/urls.py | 19 + .../admin/inventory/storages/views.py | 456 +++ .../dashboards/admin/inventory/tables.py | 1102 +++++++ .../dashboards/admin/inventory/tabs.py | 671 +++++ .../templates/inventory/_cpuprofiles.html | 9 + .../_cpuprofiles_cpuassignments.html | 10 + .../templates/inventory/_create.html | 25 + .../inventory/_detail_cpufunctions.html | 25 + .../templates/inventory/_detail_devices.html | 9 + .../inventory/_detail_interfaces.html | 9 + .../templates/inventory/_detail_lldp.html | 9 + .../inventory/_detail_local_volume_group.html | 13 + .../_detail_local_volume_group_overview.html | 42 + .../templates/inventory/_detail_memorys.html | 9 + .../inventory/_detail_neighbour.html | 94 + .../templates/inventory/_detail_overview.html | 95 + .../inventory/_detail_physical_volume.html | 54 + .../templates/inventory/_detail_ports.html | 9 + .../templates/inventory/_detail_sensor.html | 56 + .../inventory/_detail_sensor_group.html | 48 + .../templates/inventory/_detail_sensors.html | 42 + .../templates/inventory/_detail_storages.html | 28 + .../templates/inventory/_deviceusage.html | 9 + .../templates/inventory/_disk_info.html | 30 + .../templates/inventory/_diskprofiles.html | 10 + .../inventory/_diskprofiles_diskconfig.html | 12 + .../_diskprofiles_diskconfig_obs.html | 1 + .../inventory/_diskprofiles_lvgconfig.html | 15 + .../_diskprofiles_partitionconfig.html | 9 + .../inventory/_diskprofiles_storconfig.html | 25 + .../inventory/templates/inventory/_hosts.html | 41 + .../inventory/_interfaceprofiles.html | 9 + .../_interfaceprofiles_interfaceconfig.html | 22 + .../_interfaceprofiles_portconfig.html | 11 + .../templates/inventory/_memoryprofiles.html | 9 + .../_memoryprofiles_memoryassignments.html | 21 + .../templates/inventory/_update.html | 26 + .../inventory/templates/inventory/banner.html | 6 + .../_cpufunction_processorcores.html | 5 + .../cpu_functions/_createprofile.html | 42 + .../inventory/cpu_functions/_update.html | 24 + .../cpu_functions/createprofile.html | 11 + .../inventory/cpu_functions/update.html | 11 + .../inventory/templates/inventory/create.html | 11 + .../inventory/templates/inventory/detail.html | 99 + .../inventory/devices/_detail_overview.html | 37 + .../templates/inventory/devices/_update.html | 24 + .../templates/inventory/devices/_usage.html | 16 + .../templates/inventory/devices/detail.html | 8 + .../templates/inventory/devices/update.html | 11 + .../templates/inventory/devices/usage.html | 17 + .../inventory/templates/inventory/index.html | 64 + .../inventory/interfaces/_create.html | 44 + .../inventory/interfaces/_createprofile.html | 61 + .../interfaces/_detail_overview.html | 40 + .../inventory/interfaces/_update.html | 46 + .../inventory/interfaces/address/_create.html | 25 + .../inventory/interfaces/address/create.html | 11 + .../inventory/interfaces/create.html | 11 + .../inventory/interfaces/createprofile.html | 11 + .../inventory/interfaces/detail.html | 14 + .../inventory/interfaces/route/_create.html | 25 + .../inventory/interfaces/route/create.html | 11 + .../inventory/interfaces/update.html | 11 + .../inventory/memorys/_createprofile.html | 43 + .../inventory/memorys/_edit_hp_memory.html | 24 + .../memorys/_memoryfunction_hugepages.html | 3 + .../_memoryfunction_hugepages_other.html | 2 + .../memorys/_vmfunction_hugepages.html | 24 + .../memorys/_vswitchfunction_hugepages.html | 5 + .../inventory/memorys/createprofile.html | 11 + .../inventory/memorys/edit_hp_memory.html | 11 + .../inventory/ports/_detail_overview.html | 36 + .../inventory/ports/_ports_devicetype.html | 9 + .../templates/inventory/ports/_update.html | 24 + .../templates/inventory/ports/detail.html | 7 + .../templates/inventory/ports/update.html | 11 + .../inventory/sensors/_createsensorgroup.html | 27 + .../inventory/sensors/_sensor_actions.html | 3 + .../sensors/_sensorgroup_actions.html | 3 + .../inventory/sensors/_updatesensorgroup.html | 24 + .../inventory/sensors/createsensorgroup.html | 12 + .../inventory/sensors/updatesensorgroup.html | 11 + .../storages/_creatediskprofile.html | 93 + .../storages/_createlocalvolumegroup.html | 27 + .../inventory/storages/_createpartition.html | 24 + .../storages/_createphysicalvolume.html | 27 + .../storages/_createstoragevolume.html | 62 + .../inventory/storages/_editpartition.html | 24 + .../storages/_editstoragevolume.html | 44 + .../inventory/storages/creatediskprofile.html | 12 + .../storages/createlocalvolumegroup.html | 12 + .../inventory/storages/createpartition.html | 12 + .../storages/createphysicalvolume.html | 12 + .../storages/createstoragevolume.html | 15 + .../inventory/storages/editpartition.html | 12 + .../inventory/storages/editstoragevolume.html | 11 + .../inventory/storages/lvg/_edit.html | 44 + .../inventory/storages/lvg/edit.html | 12 + .../inventory/templates/inventory/update.html | 11 + .../dashboards/admin/inventory/urls.py | 157 + .../dashboards/admin/inventory/views.py | 196 ++ .../dashboards/admin/inventory/workflows.py | 839 ++++++ .../dashboards/admin/providernets/__init__.py | 0 .../dashboards/admin/providernets/panel.py | 34 + .../providernets/providernets/__init__.py | 0 .../admin/providernets/providernets/forms.py | 160 + .../providernets/ranges/__init__.py | 0 .../providernets/providernets/ranges/forms.py | 252 ++ .../providernets/ranges/tables.py | 131 + .../providernets/providernets/ranges/tabs.py | 42 + .../providernets/providernets/ranges/urls.py | 34 + .../providernets/providernets/ranges/views.py | 183 ++ .../admin/providernets/providernets/tables.py | 168 ++ .../admin/providernets/providernets/urls.py | 57 + .../admin/providernets/providernets/views.py | 185 ++ .../dashboards/admin/providernets/tabs.py | 48 + .../providernets/providernets/_add_range.html | 25 + .../providernets/providernets/_create.html | 25 + .../providernets/_detail_overview.html | 30 + .../providernets/providernets/_update.html | 29 + .../providernets/providernets/add_range.html | 11 + .../providernets/providernets/create.html | 11 + .../providernets/providernets/detail.html | 14 + .../providernets/ranges/_create.html | 25 + .../providernets/ranges/_detail_overview.html | 24 + .../providernets/ranges/_update.html | 29 + .../providernets/ranges/_vxlan.html | 3 + .../providernets/ranges/create.html | 11 + .../providernets/ranges/detail.html | 11 + .../providernets/ranges/update.html | 11 + .../providernets/providernets/update.html | 11 + .../templates/providernets/tabs.html | 15 + .../dashboards/admin/providernets/urls.py | 23 + .../dashboards/admin/providernets/views.py | 20 + .../admin/server_groups/__init__.py | 0 .../dashboards/admin/server_groups/forms.py | 200 ++ .../dashboards/admin/server_groups/panel.py | 35 + .../dashboards/admin/server_groups/tables.py | 320 ++ .../dashboards/admin/server_groups/tabs.py | 58 + .../templates/server_groups/_attach.html | 26 + .../templates/server_groups/_create.html | 27 + .../server_groups/_detail_overview.html | 53 + .../templates/server_groups/attach.html | 11 + .../templates/server_groups/create.html | 11 + .../templates/server_groups/detail.html | 11 + .../templates/server_groups/index.html | 11 + .../dashboards/admin/server_groups/urls.py | 39 + .../dashboards/admin/server_groups/views.py | 154 + .../admin/software_management/__init__.py | 0 .../admin/software_management/forms.py | 266 ++ .../admin/software_management/panel.py | 34 + .../admin/software_management/tables.py | 740 +++++ .../admin/software_management/tabs.py | 134 + .../_create_patch_strategy.html | 35 + .../_create_upgrade_strategy.html | 35 + .../software_management/_detail_patches.html | 54 + .../software_management/_detail_stage.html | 57 + .../_patch_orchestration.html | 43 + .../software_management/_patches.html | 20 + .../_upgrade_orchestration.html | 39 + .../software_management/_upload_patch.html | 27 + .../create_patch_strategy.html | 11 + .../create_upgrade_strategy.html | 11 + .../templates/software_management/index.html | 45 + .../software_management/upload_patch.html | 11 + .../admin/software_management/urls.py | 41 + .../admin/software_management/views.py | 166 ++ .../admin/storage_overview/__init__.py | 0 .../admin/storage_overview/constants.py | 12 + .../admin/storage_overview/panel.py | 34 + .../admin/storage_overview/tables.py | 58 + .../dashboards/admin/storage_overview/tabs.py | 94 + .../_detail_storageservices.html | 55 + .../templates/storage_overview/_usage.html | 5 + .../storage_overview/storage_overview.html | 8 + .../dashboards/admin/storage_overview/urls.py | 15 + .../admin/storage_overview/views.py | 22 + .../admin/system_config/__init__.py | 0 .../system_config/address_pools/__init__.py | 0 .../system_config/address_pools/forms.py | 141 + .../system_config/address_pools/tables.py | 102 + .../system_config/address_pools/views.py | 81 + .../dashboards/admin/system_config/forms.py | 938 ++++++ .../dashboards/admin/system_config/panel.py | 37 + .../dashboards/admin/system_config/tables.py | 440 +++ .../dashboards/admin/system_config/tabs.py | 260 ++ .../templates/system_config/_cdns_table.html | 7 + .../templates/system_config/_cntp_table.html | 7 + .../templates/system_config/_coam_table.html | 7 + .../_create_sdn_controller_table.html | 25 + .../_detail_sdn_controller_table.html | 18 + .../templates/system_config/_edit.html | 25 + .../system_config/_sdn_controller_table.html | 7 + .../system_config/_storage_pools_table.html | 7 + .../system_config/_storage_table.html | 7 + .../templates/system_config/_systems.html | 7 + .../system_config/_update_cdns_table.html | 25 + .../system_config/_update_cntp_table.html | 30 + .../system_config/_update_coam_table.html | 31 + .../_update_istorage_pools_table.html | 55 + .../system_config/_update_istorage_table.html | 52 + .../_update_sdn_controller_table.html | 29 + .../system_config/_update_system.html | 25 + .../system_config/address_pools/_create.html | 25 + .../system_config/address_pools/_update.html | 29 + .../system_config/address_pools/create.html | 11 + .../system_config/address_pools/update.html | 11 + .../create_sdn_controller_table.html | 11 + .../detail_sdn_controller_table.html | 11 + .../templates/system_config/edit.html | 11 + .../templates/system_config/index.html | 16 + .../system_config/update_cdns_table.html | 11 + .../system_config/update_cntp_table.html | 11 + .../system_config/update_coam_table.html | 11 + .../update_istorage_pools_table.html | 11 + .../system_config/update_istorage_table.html | 11 + .../update_sdn_controller_table.html | 11 + .../system_config/update_system.html | 11 + .../dashboards/admin/system_config/urls.py | 75 + .../dashboards/admin/system_config/views.py | 445 +++ .../dashboards/dc_admin/__init__.py | 0 .../dc_admin/cloud_overview/__init__.py | 0 .../dc_admin/cloud_overview/panel.py | 17 + .../templates/cloud_overview/index.html | 20 + .../dc_admin/cloud_overview/urls.py | 17 + .../dc_admin/cloud_overview/views.py | 14 + .../dashboards/dc_admin/dashboard.py | 33 + .../cloud_overview/cloud_overview.module.js | 34 + .../cloud_overview.module.spec.js | 18 + .../table/add_subcloud.service.js | 90 + .../table/central_table.controller.js | 115 + .../cloud_overview/table/central_table.html | 107 + .../table/subcloud_table.controller.js | 411 +++ .../cloud_overview/table/subcloud_table.html | 230 ++ .../dashboard/dc_admin/dc_admin.module.js | 24 + .../dc_admin/dc_admin.module.spec.js | 32 + .../dashboard/dc_admin/dc_manager.service.js | 176 ++ .../dashboard/dc_admin/sysinv.service.js | 63 + cgcs_dashboard/dashboards/project/__init__.py | 0 .../project/server_groups/__init__.py | 0 .../dashboards/project/server_groups/forms.py | 172 ++ .../dashboards/project/server_groups/panel.py | 52 + .../project/server_groups/tables.py | 293 ++ .../dashboards/project/server_groups/tabs.py | 58 + .../templates/server_groups/_attach.html | 26 + .../templates/server_groups/_create.html | 27 + .../server_groups/_detail_overview.html | 53 + .../templates/server_groups/attach.html | 11 + .../templates/server_groups/create.html | 11 + .../templates/server_groups/detail.html | 11 + .../templates/server_groups/index.html | 11 + .../dashboards/project/server_groups/urls.py | 39 + .../dashboards/project/server_groups/views.py | 154 + .../_1031_WRS_project_server_groups_panel.py | 10 + .../_2005_admin_platform_panel_group.py | 8 + .../_2032_WRS_admin_fault_management_panel.py | 10 + ..._WRS_platform_software_management_panel.py | 10 + .../_2035_WRS_admin_inventory_panel.py | 9 + .../enabled/_2036_WRS_admin_providernets.py | 10 + .../_2037_WRS_admin_host_topology_panel.py | 10 + ...038_WRS_platform_storage_overview_panel.py | 10 + .../enabled/_2039_WRS_admin_system_config.py | 10 + .../_2061_WRS_admin_server_groups_panel.py | 10 + cgcs_dashboard/enabled/_2200_WRS_dc_admin.py | 14 + ..._2210_WRS_dc_admin_cloud_overview_panel.py | 17 + cgcs_dashboard/enabled/__init__.py | 0 cgcs_dashboard/exceptions.py | 31 + .../static/js/horizon.hosttopology.js | 965 ++++++ cgcs_dashboard/version.py | 16 + manage.py | 23 + requirements.txt | 68 + setup.cfg | 29 + setup.py | 29 + test-requirements.txt | 30 + 365 files changed, 30125 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.rst create mode 100644 HACKING.rst create mode 100644 LICENSE create mode 100644 cgcs_dashboard/__init__.py create mode 100755 cgcs_dashboard/api/__init__.py create mode 100644 cgcs_dashboard/api/ceilometer.py create mode 100755 cgcs_dashboard/api/ceph.py create mode 100755 cgcs_dashboard/api/dc_manager.py create mode 100755 cgcs_dashboard/api/iservice.py create mode 100755 cgcs_dashboard/api/patch.py create mode 100755 cgcs_dashboard/api/rest/__init__.py create mode 100755 cgcs_dashboard/api/rest/dc_manager.py create mode 100755 cgcs_dashboard/api/rest/sysinv.py create mode 100755 cgcs_dashboard/api/sysinv.py create mode 100755 cgcs_dashboard/api/vim.py create mode 100644 cgcs_dashboard/dashboards/__init__.py create mode 100644 cgcs_dashboard/dashboards/admin/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/panel.py create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_active_alarms.html create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_history.html create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_log.html create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_summary.html create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/index.html create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/fault_management/views.py create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/panel.py create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/_svg_element.html create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/_detail_alarms.html create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/providernet.html create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/tabbed_detail.html create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/index.html create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/host_topology/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/cpu_functions/__init__.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/cpu_functions/forms.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/cpu_functions/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/cpu_functions/utils.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/cpu_functions/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/devices/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/devices/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/devices/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/devices/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/address/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/address/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/address/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/address/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/route/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/route/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/route/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/route/views.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/interfaces/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/interfaces/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/lldp/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/lldp/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/lldp/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/memorys/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/memorys/forms.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/memorys/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/memorys/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/panel.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/ports/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/ports/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/ports/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/ports/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/ports/views.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/sensors/__init__.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/sensors/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/sensors/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/sensors/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/storages/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/storages/forms.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/lvg_params/__init__.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/lvg_params/forms.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/lvg_params/tables.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/lvg_params/urls.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/lvg_params/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/storages/tables.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/tabs.py create mode 100644 cgcs_dashboard/dashboards/admin/inventory/storages/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/storages/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_cpuprofiles.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_cpuprofiles_cpuassignments.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_cpufunctions.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_devices.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_interfaces.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_lldp.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_memorys.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_neighbour.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_physical_volume.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_ports.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor_group.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensors.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_storages.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_deviceusage.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_disk_info.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig_obs.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_lvgconfig.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_partitionconfig.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_storconfig.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_hosts.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_interfaceconfig.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_portconfig.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles_memoryassignments.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/banner.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_cpufunction_processorcores.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_createprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/createprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/detail.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_update.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_usage.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/detail.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/update.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/usage.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/index.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_createprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/createprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/detail.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/create.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_createprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_edit_hp_memory.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages_other.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vmfunction_hugepages.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vswitchfunction_hugepages.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/createprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/edit_hp_memory.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_ports_devicetype.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/detail.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_createsensorgroup.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensor_actions.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensorgroup_actions.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_updatesensorgroup.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/createsensorgroup.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/updatesensorgroup.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_creatediskprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createlocalvolumegroup.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createpartition.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createphysicalvolume.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createstoragevolume.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editpartition.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editstoragevolume.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/creatediskprofile.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createlocalvolumegroup.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createpartition.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createphysicalvolume.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createstoragevolume.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editpartition.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editstoragevolume.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/_edit.html create mode 100644 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/edit.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/templates/inventory/update.html create mode 100755 cgcs_dashboard/dashboards/admin/inventory/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/views.py create mode 100755 cgcs_dashboard/dashboards/admin/inventory/workflows.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/panel.py create mode 100644 cgcs_dashboard/dashboards/admin/providernets/providernets/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/forms.py create mode 100644 cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/views.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/providernets/views.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_add_range.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_update.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/add_range.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/create.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/detail.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_update.html create mode 100644 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_vxlan.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/create.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/detail.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/update.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/update.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/templates/providernets/tabs.html create mode 100755 cgcs_dashboard/dashboards/admin/providernets/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/providernets/views.py create mode 100644 cgcs_dashboard/dashboards/admin/server_groups/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/panel.py create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_attach.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/attach.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/create.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/detail.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/index.html create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/server_groups/views.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/panel.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_patch_strategy.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_upgrade_strategy.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_patches.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_stage.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patch_orchestration.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patches.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upgrade_orchestration.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upload_patch.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_patch_strategy.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_upgrade_strategy.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/index.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/templates/software_management/upload_patch.html create mode 100755 cgcs_dashboard/dashboards/admin/software_management/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/software_management/views.py create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/__init__.py create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/constants.py create mode 100755 cgcs_dashboard/dashboards/admin/storage_overview/panel.py create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/tables.py create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/tabs.py create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_detail_storageservices.html create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_usage.html create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/storage_overview.html create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/urls.py create mode 100644 cgcs_dashboard/dashboards/admin/storage_overview/views.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/address_pools/__init__.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/address_pools/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/address_pools/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/address_pools/views.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/forms.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/panel.py create mode 100644 cgcs_dashboard/dashboards/admin/system_config/tables.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/tabs.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cdns_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cntp_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_coam_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_create_sdn_controller_table.html create mode 100644 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_detail_sdn_controller_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_edit.html create mode 100644 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_sdn_controller_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_pools_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_systems.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cdns_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cntp_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_coam_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_pools_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_sdn_controller_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_system.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_create.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_update.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/create.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/update.html create mode 100644 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/create_sdn_controller_table.html create mode 100644 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/detail_sdn_controller_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/edit.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/index.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cdns_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cntp_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_coam_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_pools_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_table.html create mode 100644 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_sdn_controller_table.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_system.html create mode 100755 cgcs_dashboard/dashboards/admin/system_config/urls.py create mode 100755 cgcs_dashboard/dashboards/admin/system_config/views.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/__init__.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/cloud_overview/__init__.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/cloud_overview/panel.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/cloud_overview/templates/cloud_overview/index.html create mode 100755 cgcs_dashboard/dashboards/dc_admin/cloud_overview/urls.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/cloud_overview/views.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/dashboard.py create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.spec.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/add_subcloud.service.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.controller.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.html create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.controller.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.html create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.spec.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_manager.service.js create mode 100755 cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/sysinv.service.js create mode 100644 cgcs_dashboard/dashboards/project/__init__.py create mode 100644 cgcs_dashboard/dashboards/project/server_groups/__init__.py create mode 100755 cgcs_dashboard/dashboards/project/server_groups/forms.py create mode 100755 cgcs_dashboard/dashboards/project/server_groups/panel.py create mode 100755 cgcs_dashboard/dashboards/project/server_groups/tables.py create mode 100755 cgcs_dashboard/dashboards/project/server_groups/tabs.py create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_attach.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_create.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_detail_overview.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/attach.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/create.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/detail.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/index.html create mode 100755 cgcs_dashboard/dashboards/project/server_groups/urls.py create mode 100755 cgcs_dashboard/dashboards/project/server_groups/views.py create mode 100755 cgcs_dashboard/enabled/_1031_WRS_project_server_groups_panel.py create mode 100755 cgcs_dashboard/enabled/_2005_admin_platform_panel_group.py create mode 100755 cgcs_dashboard/enabled/_2032_WRS_admin_fault_management_panel.py create mode 100755 cgcs_dashboard/enabled/_2034_WRS_platform_software_management_panel.py create mode 100755 cgcs_dashboard/enabled/_2035_WRS_admin_inventory_panel.py create mode 100755 cgcs_dashboard/enabled/_2036_WRS_admin_providernets.py create mode 100755 cgcs_dashboard/enabled/_2037_WRS_admin_host_topology_panel.py create mode 100755 cgcs_dashboard/enabled/_2038_WRS_platform_storage_overview_panel.py create mode 100755 cgcs_dashboard/enabled/_2039_WRS_admin_system_config.py create mode 100755 cgcs_dashboard/enabled/_2061_WRS_admin_server_groups_panel.py create mode 100755 cgcs_dashboard/enabled/_2200_WRS_dc_admin.py create mode 100755 cgcs_dashboard/enabled/_2210_WRS_dc_admin_cloud_overview_panel.py create mode 100644 cgcs_dashboard/enabled/__init__.py create mode 100755 cgcs_dashboard/exceptions.py create mode 100755 cgcs_dashboard/static/js/horizon.hosttopology.js create mode 100755 cgcs_dashboard/version.py create mode 100755 manage.py create mode 100755 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 test-requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5138969c --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +*.egg* +*.mo +*.pyc +*.sw? +*.sqlite3 +*.lock +.environment_version +.selenium_log +.coverage* +.noseids +.DS_STORE +.DS_Store +/cover +coverage.xml +coverage-karma +nosetests.xml +pep8.txt +pylint.txt +# Files created by releasenotes build +releasenotes/build +reports +openstack_dashboard/local/* +!openstack_dashboard/local/local_settings.py.example +!openstack_dashboard/local/enabled/_50__settings.py.example +!openstack_dashboard/local/local_settings.d +openstack_dashboard/local/local_settings.d/* +!openstack_dashboard/local/local_settings.d/*.example +openstack_dashboard/test/.secret_key_store +openstack_dashboard/test/integration_tests/local-horizon.conf +openstack_dashboard/test/integration_tests/test_reports/ +openstack_dashboard/wsgi/horizon.wsgi +doc/build/ +doc/source/sourcecode +/static/ +integration_tests_screenshots/ +.venv +.tox +node_modules +npm-debug.log +build +dist +AUTHORS +ChangeLog +tags +ghostdriver.log +.testrepository +.idea diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..7095b253 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,18 @@ +If you would like to contribute to the development of OpenStack, +you must follow the steps documented at: + + http://docs.openstack.org/developer/horizon/contributing.html + + or http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Once those steps have been completed, changes to OpenStack +should be submitted for review via the Gerrit tool, following +the workflow documented at: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/horizon diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 00000000..19c7d9d3 --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,15 @@ +Horizon Style Commandments +========================== + +- Step 1: Read the OpenStack Style Commandments + http://docs.openstack.org/developer/hacking/ +- Step 2: Read [hacking] section in tox.ini to find the list of names which + can be imported directly without triggering the "H302: import only modules" + flake8 warning +- Step 3: Read on + +Horizon Specific Commandments +----------------------------- + +- Read the Horizon contributing documentation at http://docs.openstack.org/developer/horizon/contributing.html +- [M322] Method's default argument shouldn't be mutable. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..68c771a0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/cgcs_dashboard/__init__.py b/cgcs_dashboard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/api/__init__.py b/cgcs_dashboard/api/__init__.py new file mode 100755 index 00000000..fd2aaad6 --- /dev/null +++ b/cgcs_dashboard/api/__init__.py @@ -0,0 +1,31 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cgcs_dashboard.api import ceilometer +from cgcs_dashboard.api import ceph +from cgcs_dashboard.api import dc_manager +from cgcs_dashboard.api import iservice +from cgcs_dashboard.api import patch +from cgcs_dashboard.api import sysinv +from cgcs_dashboard.api import vim + + +__all__ = [ + "ceilometer", + "ceph", + "dc_manager", + "iservice", + "patch", + "sysinv", + "vim", +] diff --git a/cgcs_dashboard/api/ceilometer.py b/cgcs_dashboard/api/ceilometer.py new file mode 100644 index 00000000..7be295be --- /dev/null +++ b/cgcs_dashboard/api/ceilometer.py @@ -0,0 +1,52 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from ceilometerclient import client as ceilometer_client +from django.conf import settings +from openstack_dashboard.api import base + +from horizon.utils.memoized import memoized # noqa + + +class Pipeline(base.APIResourceWrapper): + """Represents one Ceilometer pipeline entry.""" + + _attrs = ['name', 'enabled', 'meters', 'location', 'max_bytes', + 'backup_count', 'compress'] + + def __init__(self, apipipeline): + super(Pipeline, self).__init__(apipipeline) + + +@memoized +def ceilometerclient(request): + """Initialization of Ceilometer client.""" + + endpoint = base.url_for(request, 'metering') + insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) + cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None) + return ceilometer_client.Client('2', endpoint, + token=(lambda: request.user.token.id), + insecure=insecure, + cacert=cacert) + + +def pipeline_list(request): + """List the configured pipeline.""" + pipeline_entries = ceilometerclient(request).pipelines.list() + pipelines = [Pipeline(p) for p in pipeline_entries] + return pipelines + + +def pipeline_update(request, pipeline_name, some_dict): + pipeline = ceilometerclient(request).pipelines.update(pipeline_name, + **some_dict) + if not pipeline: + raise ValueError( + 'No match found for pipeline_name "%s".' % pipeline_name) + return Pipeline(pipeline) diff --git a/cgcs_dashboard/api/ceph.py b/cgcs_dashboard/api/ceph.py new file mode 100755 index 00000000..b2834f8a --- /dev/null +++ b/cgcs_dashboard/api/ceph.py @@ -0,0 +1,187 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from __future__ import absolute_import + +import logging + +from cephclient import wrapper + +from openstack_dashboard.api import base + +LOG = logging.getLogger(__name__) + + +# TODO(wrs) this can be instancized once, or will need to pass request per +# user? +def cephwrapper(): + return wrapper.CephWrapper() + + +class Monitor(base.APIDictWrapper): + __attrs = ['host', 'rank'] + + def __init__(self, apidict): + super(Monitor, self).__init__(apidict) + + +class OSD(base.APIDictWrapper): + __attrs = ['id', 'name', 'host', 'status'] + + def __init__(self, apidict): + super(OSD, self).__init__(apidict) + + +class Cluster(base.APIDictWrapper): + _attrs = ['fsid', 'status', 'health', 'detail'] + + def __init__(self, apidict): + super(Cluster, self).__init__(apidict) + + +class Storage(base.APIDictWrapper): + _attrs = ['total', 'used', 'available', + 'writes_per_sec', 'operations_per_sec'] + + def __init__(self, apidict): + super(Storage, self).__init__(apidict) + + +def _Bytes_to_MiB(value_B): + return (value_B / (1024 * 1024)) + + +def _Bytes_to_GiB(value_B): + return (value_B / (1024 * 1024 * 1024)) + + +def cluster_get(): + # the json response doesn't give all the information + response, text_body = cephwrapper().health(body='text') + # ceph is not up, raise exception + if not response.ok: + response.raise_for_status() + health_info = text_body.split(' ', 1) + + # if health is ok, there will be no details so just show HEALTH_OK + if len(health_info) > 1: + detail = health_info[1] + else: + detail = health_info[0] + + response, cluster_uuid = cephwrapper().fsid(body='text') + if not response.ok: + cluster_uuid = None + + cluster = { + 'fsid': cluster_uuid, + 'health': health_info[0], + 'detail': detail, + } + + return Cluster(cluster) + + +def storage_get(): + # # Space info + response, body = cephwrapper().df(body='json') + # return no space info + if not response.ok: + response.raise_for_status() + stats = body['output']['stats'] + space = { + 'total': _Bytes_to_GiB(stats['total_bytes']), + 'used': _Bytes_to_MiB(stats['total_used_bytes']), + 'available': _Bytes_to_GiB(stats['total_avail_bytes']), + } + + # # I/O info + response, body = cephwrapper().osd_pool_stats(body='json', + name='cinder-volumes') + if not response.ok: + response.raise_for_status() + stats = body['output'][0]['client_io_rate'] + # not showing reads/sec at the moment + # reads_per_sec = stats['read_bytes_sec'] if ( + # 'read_bytes_sec' in stats) else 0 + writes_per_sec = stats['write_bytes_sec'] if ( + 'write_bytes_sec' in stats) else 0 + operations_per_sec = stats['op_per_sec'] if ('op_per_sec' in stats) else 0 + io = { + 'writes_per_sec': writes_per_sec / 1024, + 'operations_per_sec': operations_per_sec + } + + storage = {} + storage.update(space) + storage.update(io) + + return Storage(storage) + + +def _get_quorum_status(mon, quorums): + if mon['rank'] in quorums: + status = 'up' + else: + status = 'down' + return status + + +def monitor_list(): + response, body = cephwrapper().mon_dump(body='json') + # return no monitors info + if not response.ok: + response.raise_for_status() + + quorums = body['output']['quorum'] + + mons = [] + for mon in body['output']['mons']: + status = _get_quorum_status(mon, quorums) + mons.append( + {'host': mon['name'], 'rank': mon['rank'], 'status': status}) + return [Monitor(m) for m in mons] + + +def osd_list(): + # would use osd_find, but it doesn't give osd's name + response, tree = cephwrapper().osd_tree(body='json') + if not response.ok: + response.raise_for_status() + + osds = [] + for node in tree['output']['nodes']: + # found osd + if node['type'] == 'osd': + osd = {} + osd['id'] = node['id'] + osd['name'] = node['name'] + osd['status'] = node['status'] + + # check if osd belongs to host + response, body = cephwrapper().osd_find(body='json', id=osd['id']) + if response.ok and 'host' in body['output']['crush_location']: + osd['host'] = body['output']['crush_location']['host'] + # else dont set hostname + + osds.append(osd) + + return [OSD(o) for o in osds] diff --git a/cgcs_dashboard/api/dc_manager.py b/cgcs_dashboard/api/dc_manager.py new file mode 100755 index 00000000..7ac5f745 --- /dev/null +++ b/cgcs_dashboard/api/dc_manager.py @@ -0,0 +1,73 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from dcmanagerclient.api.v1 import client + +from horizon.utils.memoized import memoized # noqa + +from openstack_dashboard.api import base + +LOG = logging.getLogger(__name__) + + +@memoized +def dcmanagerclient(request): + endpoint = base.url_for(request, 'dcmanager', 'adminURL') + c = client.Client(project_id=request.user.project_id, + user_id=request.user.id, + auth_token=request.user.token.id, + dcmanager_url=endpoint) + return c + + +class Summary(base.APIResourceWrapper): + _attrs = ['name', 'critical', 'major', 'minor', 'warnings', 'status'] + + +def alarm_summary_list(request): + summaries = dcmanagerclient(request).alarm_manager.list_alarms() + return [Summary(summary) for summary in summaries] + + +class Subcloud(base.APIResourceWrapper): + _attrs = ['subcloud_id', 'name', 'description', 'location', + 'software_version', 'management_subnet', 'management_state', + 'availability_status', 'management_start_ip', + 'management_end_ip', 'management_gateway_ip', + 'systemcontroller_gateway_ip', 'created_at', 'updated_at', + 'sync_status', 'endpoint_sync_status', ] + + +def subcloud_list(request): + subclouds = dcmanagerclient(request).subcloud_manager.list_subclouds() + return [Subcloud(subcloud) for subcloud in subclouds] + + +def subcloud_create(request, data): + return dcmanagerclient(request).subcloud_manager.add_subcloud( + **data.get('data')) + + +def subcloud_update(request, subcloud_id, changes): + response = dcmanagerclient(request).subcloud_manager.update_subcloud( + subcloud_id, **changes.get('updated')) + # Updating returns a list of subclouds for some reason + return [Subcloud(subcloud) for subcloud in response] + + +def subcloud_delete(request, subcloud_id): + return dcmanagerclient(request).subcloud_manager.delete_subcloud( + subcloud_id) + + +def subcloud_generate_config(request, subcloud_id, data): + return dcmanagerclient(request).subcloud_manager.generate_config_subcloud( + subcloud_id, **data) diff --git a/cgcs_dashboard/api/iservice.py b/cgcs_dashboard/api/iservice.py new file mode 100755 index 00000000..7754bfb5 --- /dev/null +++ b/cgcs_dashboard/api/iservice.py @@ -0,0 +1,61 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from __future__ import absolute_import + +import logging + +from django.conf import settings +import sm_client as smc + +# Swap out with SM API +LOG = logging.getLogger(__name__) + + +def sm_client(request): + # o = urlparse.urlparse(url_for(request, 'inventory')) + # url = "://".join((o.scheme, o.netloc)) + insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) + # LOG.debug('sysinv client conn using token "%s" and url "%s"' + # % (request.user.token.id, url)) + # return smc.Client('1', url, token=request.user.token.id, + # insecure=insecure) + + return smc.Client('1', 'http://localhost:7777', + token=request.user.token.id, + insecure=insecure) + + +def sm_sda_list(request): + sdas = sm_client(request).sm_sda.list() + LOG.debug("SM sdas list %s", sdas) + + # fields = ['uuid', 'service_group_name', 'node_name', 'state', 'status', + # 'condition'] + return sdas + + +def sm_nodes_list(request): + nodes = sm_client(request).sm_nodes.list() + LOG.debug("SM nodes list %s", nodes) + + # fields = ['id', 'name', 'state', 'online'] + return nodes diff --git a/cgcs_dashboard/api/patch.py b/cgcs_dashboard/api/patch.py new file mode 100755 index 00000000..bdb08561 --- /dev/null +++ b/cgcs_dashboard/api/patch.py @@ -0,0 +1,239 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging +import urlparse + +import requests + +from openstack_dashboard.api import base +from requests_toolbelt import MultipartEncoder + + +LOG = logging.getLogger(__name__) + + +class Client(object): + def __init__(self, version, url, token_id): + self.version = version + self.url = url + self.token_id = token_id + + def _make_request(self, token_id, method, api_version, api_cmd, + encoder=None): + url = self.url + url += "/%s/%s" % (api_version, api_cmd) + # url += "/patch/%s" % (api_cmd) + + headers = {"X-Auth-Token": token_id, + "Accept": "application/json"} + + if method == 'GET': + req = requests.get(url, headers=headers) + elif method == 'POST': + if encoder is not None: + headers['Content-Type'] = encoder.content_type + req = requests.post(url, headers=headers, data=encoder) + + resp = req.json() + + return resp + + def get_patches(self): + return self._make_request(self.token_id, "GET", self.version, + "query?show=all") + + def show_patch(self, patch_id): + return self._make_request(self.token_id, "GET", self.version, + "show/%s" % patch_id) + + def get_hosts(self): + return self._make_request(self.token_id, "GET", self.version, + "query_hosts") + + def upload(self, file): + encoder = MultipartEncoder(fields=file) + return self._make_request(self.token_id, "POST", self.version, + "upload", encoder=encoder) + + def apply(self, patch_ids): + patches = "/".join(patch_ids) + return self._make_request(self.token_id, "POST", self.version, + "apply/%s" % patches) + + def remove(self, patch_ids): + patches = "/".join(patch_ids) + return self._make_request(self.token_id, "POST", self.version, + "remove/%s" % patches) + + def delete(self, patch_ids): + patches = "/".join(patch_ids) + return self._make_request(self.token_id, "POST", self.version, + "delete/%s" % patches) + + def host_install(self, host): + return self._make_request(self.token_id, "POST", self.version, + "host_install/%s" % host) + + def host_install_async(self, host): + return self._make_request(self.token_id, "POST", self.version, + "host_install_async/%s" % host) + + +def _patching_client(request): + o = urlparse.urlparse(base.url_for(request, 'patching')) + url = "://".join((o.scheme, o.netloc)) + return Client("v1", url, token_id=request.user.token.id) + + +class Patch(object): + _attrs = ['status', + 'sw_version', + 'install_instructions', + 'description', + 'warnings', + 'summary', + 'repostate', + 'patchstate', + 'requires', + 'unremovable', + 'reboot_required'] + + +class Host(object): + _attrs = ['hostname', + 'installed', + 'ip', + 'missing_pkgs', + 'nodetype', + 'patch_current', + 'patch_failed', + 'requires_reboot', + 'secs_since_ack', + 'stale_details', + 'to_remove', + 'sw_version', + 'state', + 'allow_insvc_patching', + 'interim_state'] + + +def get_patches(request): + patches = [] + try: + info = _patching_client(request).get_patches() + except Exception: + return patches + + if info: + for p in info['pd'].iteritems(): + patch = Patch() + + for a in patch._attrs: + if a == 'requires': + patch.requires = [str(rp) for rp in p[1][a]] + continue + + if a == 'reboot_required': + # Default to "N" + setattr(patch, a, str(p[1].get(a, "N"))) + continue + + # Must handle older patches that have metadata that is missing + # newer attributes. Default missing attributes to "". + setattr(patch, a, str(p[1].get(a, ""))) + patch.patch_id = str(p[0]) + + patches.append(patch) + return patches + + +def get_patch(request, patch_id): + patches = get_patches(request) + patch = next((p for p in patches if p.patch_id == patch_id), None) + + # add on patch contents + data = _patching_client(request).show_patch(patch_id) + patch.contents = [str(pkg) for pkg in data['contents'][patch_id]] + + return patch + + +def get_hosts(request): + hosts = [] + try: + info = _patching_client(request).get_hosts() + except Exception: + return hosts + + if info: + for h in info['data']: + host = Host() + for a in host._attrs: + setattr(host, a, h[a]) + hosts.append(host) + return hosts + + +def get_host(request, hostname): + phosts = get_hosts(request) + return next((phost for phost in phosts if phost.hostname == hostname), + None) + + +def get_message(data): + LOG.info("RESPONSE: %s", data) + if not data or ('error' in data and data["error"] != ""): + raise ValueError(data["error"] or "Invalid patch file") + if 'warning' in data and data["warning"] != "": + return data["warning"] + if 'info' in data and data["info"] != "": + return data["info"] + return "" + + +def upload_patch(request, patchfile, name): + file = {'file': (name, patchfile,)} + resp = _patching_client(request).upload(file) + return get_message(resp) + + +def patch_apply_req(request, patch_id): + resp = _patching_client(request).apply(patch_id) + return get_message(resp) + + +def patch_remove_req(request, patch_id): + resp = _patching_client(request).remove(patch_id) + return get_message(resp) + + +def patch_delete_req(request, patch_id): + resp = _patching_client(request).delete(patch_id) + return get_message(resp) + + +def host_install(request, hostname): + resp = _patching_client(request).host_install(hostname) + return get_message(resp) + + +def host_install_async(request, hostname): + resp = _patching_client(request).host_install_async(hostname) + return get_message(resp) diff --git a/cgcs_dashboard/api/rest/__init__.py b/cgcs_dashboard/api/rest/__init__.py new file mode 100755 index 00000000..4cd383da --- /dev/null +++ b/cgcs_dashboard/api/rest/__init__.py @@ -0,0 +1,31 @@ +# Copyright 2014, Rackspace, US, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""This package holds the REST API that supports the Horizon dashboard +Javascript code. + +It is not intended to be used outside of Horizon, and makes no promises of +stability or fitness for purpose outside of that scope. + +It does not promise to adhere to the general OpenStack API Guidelines set out +in https://wiki.openstack.org/wiki/APIChangeGuidelines. +""" + +from openstack_dashboard.api.rest import dc_manager +from openstack_dashboard.api.rest import sysinv + + +__all__ = [ + 'dc_manager', + 'sysinv', +] diff --git a/cgcs_dashboard/api/rest/dc_manager.py b/cgcs_dashboard/api/rest/dc_manager.py new file mode 100755 index 00000000..966693c2 --- /dev/null +++ b/cgcs_dashboard/api/rest/dc_manager.py @@ -0,0 +1,90 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.views import generic + +from openstack_dashboard.api import dc_manager +from openstack_dashboard.api.rest import urls +from openstack_dashboard.api.rest import utils as rest_utils + +LOG = logging.getLogger(__name__) + + +@urls.register +class Subcloud(generic.View): + """API for manipulating a single subcloud""" + + url_regex = r'dc_manager/subclouds/(?P[^/]+|default)/$' + + @rest_utils.ajax(data_required=True) + def patch(self, request, subcloud_id): + """Update a specific subcloud + + PATCH http://localhost/api/dc_manager/subclouds/2 + """ + dc_manager.subcloud_update(request, subcloud_id, request.DATA) + + @rest_utils.ajax() + def delete(self, request, subcloud_id): + """Delete a specific subcloud + + DELETE http://localhost/api/dc_manager/subclouds/3 + """ + dc_manager.subcloud_delete(request, subcloud_id) + + +@urls.register +class SubcloudConfig(generic.View): + """API for Subcloud configurations.""" + url_regex = \ + r'dc_manager/subclouds/(?P[^/]+)/generate-config/$' + + @rest_utils.ajax() + def get(self, request, subcloud_id): + """Generate a config for a specific subcloud.""" + + response = dc_manager.subcloud_generate_config(request, subcloud_id, + request.GET.dict()) + response = {'config': response} + return rest_utils.CreatedResponse('/api/dc_manager/subclouds/', + response) + + +@urls.register +class SubClouds(generic.View): + """API for Distributed Cloud Subclouds""" + url_regex = r'dc_manager/subclouds/$' + + @rest_utils.ajax() + def get(self, request): + """Get a list of subclouds""" + result = dc_manager.subcloud_list(request) + return {'items': [sc.to_dict() for sc in result]} + + @rest_utils.ajax(data_required=True) + def put(self, request): + """Create a Subcloud. + + Create a subcloud using the parameters supplied in the POST + application/json object. + """ + dc_manager.subcloud_create(request, request.DATA) + + +@urls.register +class Summaries(generic.View): + """API for Distributed Cloud Alarm Summaries""" + url_regex = r'dc_manager/alarm_summaries/$' + + @rest_utils.ajax() + def get(self, request): + """Get a list of summaries""" + result = dc_manager.alarm_summary_list(request) + return {'items': [s.to_dict() for s in result]} diff --git a/cgcs_dashboard/api/rest/sysinv.py b/cgcs_dashboard/api/rest/sysinv.py new file mode 100755 index 00000000..1926694e --- /dev/null +++ b/cgcs_dashboard/api/rest/sysinv.py @@ -0,0 +1,38 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.views import generic + +from openstack_dashboard.api.rest import urls +from openstack_dashboard.api.rest import utils as rest_utils +from openstack_dashboard.api import sysinv + + +@urls.register +class AlarmSummary(generic.View): + """API for retrieving alarm summaries.""" + url_regex = r'sysinv/alarm_summary/$' + + @rest_utils.ajax() + def get(self, request): + """Get an alarm summary for the system""" + include_suppress = request.GET.get('include_suppress', False) + result = sysinv.alarm_summary_get(request, include_suppress) + return result.to_dict() + + +@urls.register +class System(generic.View): + """API for retrieving the system.""" + url_regex = r'sysinv/system/$' + + @rest_utils.ajax() + def get(self, request): + """Get the system entity""" + result = sysinv.system_get(request) + return result.to_dict() diff --git a/cgcs_dashboard/api/sysinv.py b/cgcs_dashboard/api/sysinv.py new file mode 100755 index 00000000..b462ccf4 --- /dev/null +++ b/cgcs_dashboard/api/sysinv.py @@ -0,0 +1,2601 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from __future__ import absolute_import + +import datetime +import logging + +from cgtsclient.v1 import client as cgts_client +from cgtsclient.v1 import icpu as icpu_utils + +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + +from openstack_dashboard.api import base + +import cgcs_patch.constants as patch_constants +import sysinv.common.constants as constants + +SYSTEM_TYPE_STANDARD = constants.TIS_STD_BUILD +SYSTEM_TYPE_AIO = constants.TIS_AIO_BUILD + +PERSONALITY_CONTROLLER = 'controller' +PERSONALITY_COMPUTE = 'compute' +PERSONALITY_NETWORK = 'network' +PERSONALITY_STORAGE = 'storage' +PERSONALITY_UNKNOWN = 'unknown' + +SUBFUNCTIONS_COMPUTE = 'compute' +SUBFUNCTIONS_LOWLATENCY = 'lowlatency' + +BM_TYPE_NULL = '' +BM_TYPE_NONE = 'none' +BM_TYPE_GENERIC = 'bmc' + +RECORDTYPE_INSTALL = 'install' +RECORDTYPE_INSTALL_ANSWER = 'install_answers' +RECORDTYPE_RECONFIG = 'reconfig' + +INSTALL_OUTPUT_TEXT = 'text' +INSTALL_OUTPUT_GRAPHICAL = 'graphical' + +# Sensor Actions Choices +SENSORS_AC_NOACTIONSCONFIGURABLE = "NoActionsConfigurable" +SENSORS_AC_NONE = "None" +SENSORS_AC_IGNORE = "ignore" +SENSORS_AC_LOG = "log" +SENSORS_AC_ALARM = "alarm" +SENSORS_AC_RESET = "reset" +SENSORS_AC_POWERCYCLE = "power-cycle" +SENSORS_AC_POWEROFF = "poweroff" + +# Cinder backend values +CINDER_BACKEND_LVM = "lvm" +CINDER_BACKEND_CEPH = "ceph" + +# Local Volume Group Values +LVG_NOVA_LOCAL = "nova-local" +LVG_CGTS_VG = "cgts-vg" +LVG_CINDER_VOLUMES = "cinder-volumes" +LVG_DEL = 'removing' +LVG_ADD = 'adding' +LVG_PROV = 'provisioned' + +# Physical Volume Values +PV_ADD = 'adding' +PV_DEL = 'removing' + +# Storage: Volume Group Parameter Types +LVG_NOVA_PARAM_BACKING = 'instance_backing' +LVG_NOVA_PARAM_INSTANCES_SIZE_MIB = 'instances_lv_size_mib' +LVG_NOVA_PARAM_DISK_OPS = 'concurrent_disk_operations' +LVG_NOVA_BACKING_LVM = 'lvm' +LVG_NOVA_BACKING_IMAGE = 'image' +LVG_NOVA_BACKING_REMOTE = 'remote' +LVG_CINDER_PARAM_LVM_TYPE = 'lvm_type' +LVG_CINDER_LVM_TYPE_THIN = 'thin' +LVG_CINDER_LVM_TYPE_THICK = 'default' + +# Storage: User Created Partitions +USER_PARTITION_PHYS_VOL = constants.USER_PARTITION_PHYSICAL_VOLUME +PARTITION_STATUS_MSG = constants.PARTITION_STATUS_MSG +PARTITION_IN_USE_STATUS = constants.PARTITION_IN_USE_STATUS + +# Fault management values +FM_ALL = 'ALL' +FM_ALARM = 'ALARM' +FM_LOG = 'LOG' +FM_SUPPRESS_SHOW = 'SUPPRESS_SHOW' +FM_SUPPRESS_HIDE = 'SUPPRESS_HIDE' +FM_ALL_SUPPRESS_HIDE = 'ALL|SUPPRESS_HIDE' +FM_SUPPRESSED = 'suppressed' +FM_UNSUPPRESSED = 'unsuppressed' +FM_CRITICAL = 'critical' +FM_MAJOR = 'major' +FM_MINOR = 'minor' +FM_WARNING = 'warning' +FM_NONE = 'none' + +# Host Personality Sub-Types +PERSONALITY_SUBTYPE_CEPH_BACKING = 'ceph-backing' +PERSONALITY_SUBTYPE_CEPH_CACHING = 'ceph-caching' + +# The default size of a stor's journal. This should be the same value as +# journal_default_size from sysinv.conf. +JOURNAL_DEFAULT_SIZE = 1024 + +# Platform configuration +PLATFORM_CONFIGURATION = '/etc/platform/platform.conf' + +# Neutron ML2 Service Parameters (ripped from sysinv constants) +SERVICE_TYPE_NETWORK = 'network' +SERVICE_PARAM_SECTION_NETWORK_DEFAULT = 'default' +SERVICE_PARAM_NAME_DEFAULT_SERVICE_PLUGINS = 'service_plugins' +SERVICE_PARAM_ODL_ROUTER_PLUGINS = [ + 'odl-router', + 'networking_odl.l3.l3_odl.OpenDaylightL3RouterPlugin'] + +LOG = logging.getLogger(__name__) + + +def cgtsclient(request): + insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) + cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None) + + # FIXME this returns the wrong URL + endpoint = base.url_for(request, 'platform', 'adminURL') + version = 1 + + LOG.debug('cgtsclient connection created using token "%s" and url "%s"', + request.user.token.id, endpoint) + LOG.debug('user_id=%(user)s, tenant_id=%(tenant)s', + {'user': request.user.id, 'tenant': request.user.tenant_id}) + + return cgts_client.Client(version=version, + endpoint=endpoint, + auth_url=base.url_for(request, 'identity', + 'adminURL'), + token=request.user.token.id, # os_auth_token + username=request.user.username, + password=request.user.token.id, + tenant_id=request.user.tenant_id, # os_tenant_id + insecure=insecure, cacert=cacert) + + +class Memory(base.APIResourceWrapper): + """Wrapper for Inventory System""" + + _attrs = ['numa_node', + 'memtotal_mib', + 'platform_reserved_mib', + 'memavail_mib', + 'hugepages_configured', + 'avs_hugepages_size_mib', + 'avs_hugepages_nr', + 'avs_hugepages_avail', + 'vm_hugepages_nr_2M_pending', + 'vm_hugepages_avail_2M', + 'vm_hugepages_nr_1G_pending', + 'vm_hugepages_avail_1G', + 'vm_hugepages_nr_1G', + 'vm_hugepages_nr_2M', + 'vm_hugepages_nr_4K', + 'vm_hugepages_possible_2M', + 'vm_hugepages_possible_1G', + 'vm_hugepages_use_1G', + 'uuid', 'ihost_uuid', 'inode_uuid', + 'minimum_platform_reserved_mib'] + + def __init__(self, apiresource): + super(Memory, self).__init__(apiresource) + + +class System(base.APIResourceWrapper): + """Wrapper for Inventory System""" + + _attrs = ['uuid', 'name', 'system_type', 'system_mode', 'description', + 'software_version', 'capabilities', 'updated_at', 'created_at', + 'location'] + + def __init__(self, apiresource): + super(System, self).__init__(apiresource) + + def get_short_software_version(self): + if self.software_version: + return self.software_version.split(" ")[0] + return None + + +class Node(base.APIResourceWrapper): + """Wrapper for Inventory Node (or Socket)""" + + _attrs = ['uuid', 'numa_node', 'capabilities', 'ihost_uuid'] + + def __init__(self, apiresource): + super(Node, self).__init__(apiresource) + + +class Cpu(base.APIResourceWrapper): + """Wrapper for Inventory Cpu Cores""" + + _attrs = ['id', 'uuid', 'cpu', 'numa_node', 'core', 'thread', + 'allocated_function', + 'cpu_model', 'cpu_family', + 'capabilities', + 'ihost_uuid', 'inode_uuid'] + + def __init__(self, apiresource): + super(Cpu, self).__init__(apiresource) + + +class Port(base.APIResourceWrapper): + """Wrapper for Inventory Ports""" + + _attrs = ['id', 'uuid', 'name', 'namedisplay', 'pciaddr', 'pclass', + 'pvendor', 'pdevice', 'interface_id', + 'psvendor', 'psdevice', 'numa_node', 'mac', 'mtu', 'speed', + 'link_mode', 'capabilities', 'host_uuid', 'interface_uuid', + 'bootp', 'autoneg', 'type', 'sriov_numvfs', 'sriov_totalvfs', + 'sriov_vfs_pci_address', 'driver', 'dpdksupport', 'neighbours'] + + def __init__(self, apiresource): + super(Port, self).__init__(apiresource) + self.autoneg = 'Yes' # TODO(wrs) Remove this when autoneg supported + # in DB + + def get_port_display_name(self): + if self.name: + return self.name + if self.namedisplay: + return self.namedisplay + else: + return '(' + str(self.uuid)[-8:] + ')' + + +class Disk(base.APIResourceWrapper): + """Wrapper for Inventory Disks""" + + _attrs = ['uuid', + 'device_node', + 'device_path', + 'device_id', + 'device_wwn', + 'device_num', + 'device_type', + 'size_mib', + 'available_mib', + 'rpm', + 'serial_id', + 'capabilities', + 'ihost_uuid', + 'istor_uuid', + 'ipv_uuid'] + + def __init__(self, apiresource): + super(Disk, self).__init__(apiresource) + + def get_model_num(self): + if 'model_num' in self.capabilities: + return self.capabilities['model_num'] + + +class StorageVolume(base.APIResourceWrapper): + """Wrapper for Inventory Volumes""" + + _attrs = ['uuid', + 'osdid', + 'state', + 'function', + 'capabilities', + 'idisk_uuid', + 'ihost_uuid', + 'journal_path', + 'journal_size_mib', + 'journal_location'] + + def __init__(self, apiresource): + super(StorageVolume, self).__init__(apiresource) + + +class PhysicalVolume(base.APIResourceWrapper): + """Wrapper for Physical Volumes""" + + _attrs = ['uuid', + 'pv_state', + 'pv_type', + 'disk_or_part_uuid', + 'disk_or_part_device_node', + 'disk_or_part_device_path', + 'lvm_pv_name', + 'lvm_vg_name', + 'lvm_pv_uuid', + 'lvm_pv_size', + 'lvm_pe_total', + 'lvm_pe_alloced', + 'ihost_uuid', + 'created_at', + 'updated_at'] + + def __init__(self, apiresource): + super(PhysicalVolume, self).__init__(apiresource) + + +def host_pv_list(request, host_id): + pvs = cgtsclient(request).ipv.list(host_id) + return [PhysicalVolume(n) for n in pvs] + + +def host_pv_get(request, ipv_id): + pv = cgtsclient(request).ipv.get(ipv_id) + if not pv: + raise ValueError('No match found for pv_id "%s".' % ipv_id) + return PhysicalVolume(pv) + + +def host_pv_create(request, **kwargs): + pv = cgtsclient(request).ipv.create(**kwargs) + return PhysicalVolume(pv) + + +def host_pv_delete(request, ipv_id): + return cgtsclient(request).ipv.delete(ipv_id) + + +class Partition(base.APIResourceWrapper): + """Wrapper for Inventory Partitions.""" + + _attrs = ['uuid', + 'start_mib', + 'end_mib', + 'size_mib', + 'device_path', + 'type_guid', + 'type_name', + 'idisk_uuid', + 'ipv_uuid', + 'ihost_uuid', + 'status'] + + def __init__(self, apiresource): + super(Partition, self).__init__(apiresource) + + +def host_disk_partition_list(request, host_id, disk_id=None): + partitions = cgtsclient(request).partition.list(host_id, disk_id) + return [Partition(p) for p in partitions] + + +def host_disk_partition_get(request, partition_id): + partition = cgtsclient(request).partition.get(partition_id) + if not partition: + raise ValueError('No match found for partition_id ' + '"%s".' % partition_id) + return Partition(partition) + + +def host_disk_partition_create(request, **kwargs): + partition = cgtsclient(request).partition.create(**kwargs) + return Partition(partition) + + +def host_disk_partition_update(request, partition_id, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + + partition = cgtsclient(request).partition.update(partition_id, mypatch) + return Partition(partition) + + +def host_disk_partition_delete(request, partition_id, **kwargs): + return cgtsclient(request).partition.delete(partition_id) + + +class LocalVolumeGroup(base.APIResourceWrapper): + """Wrapper for Inventory Local Volume Groups""" + + _attrs = ['lvm_vg_name', + 'vg_state', + 'uuid', + 'ihost_uuid', + 'capabilities', + 'lvm_vg_access', + 'lvm_max_lv', + 'lvm_cur_lv', + 'lvm_max_pv', + 'lvm_cur_pv', + 'lvm_vg_size', + 'lvm_vg_total_pe', + 'lvm_vg_free_pe', + 'created_at', + 'updated_at'] + + def __init__(self, apiresource): + super(LocalVolumeGroup, self).__init__(apiresource) + + +class LocalVolumeGroupParam(object): + def __init__(self, lvg_id, key, val): + self.lvg_id = lvg_id + self.id = key + self.key = key + self.value = val + + +def host_lvg_list(request, host_id, get_params=False): + lvgs = cgtsclient(request).ilvg.list(host_id) + if get_params: + for lvg in lvgs: + lvg.params = host_lvg_get_params(request, lvg.uuid, True, lvg) + return [LocalVolumeGroup(n) for n in lvgs] + + +def host_lvg_get(request, ilvg_id, get_params=False): + lvg = cgtsclient(request).ilvg.get(ilvg_id) + if not lvg: + raise ValueError('No match found for lvg_id "%s".' % ilvg_id) + if get_params: + lvg.params = host_lvg_get_params(request, lvg.id, True, lvg) + return LocalVolumeGroup(lvg) + + +def host_lvg_create(request, **kwargs): + lvg = cgtsclient(request).ilvg.create(**kwargs) + return LocalVolumeGroup(lvg) + + +def host_lvg_delete(request, ilvg_id): + return cgtsclient(request).ilvg.delete(ilvg_id) + + +def host_lvg_update(request, ilvg_id, patch): + return cgtsclient(request).ilvg.update(ilvg_id, patch) + + +def host_lvg_get_params(request, lvg_id, raw=False, lvg=None): + if lvg is None: + lvg = cgtsclient(request).ilvg.get(lvg_id) + params = lvg.capabilities + if raw: + return params + return [LocalVolumeGroupParam(lvg_id, key, value) for + key, value in params.items()] + + +class Sensor(base.APIResourceWrapper): + """Wrapper for Sensors""" + + _attrs = ['uuid', + 'status', + 'state', + 'sensortype', + 'datatype', + 'sensorname', + 'actions_critical', + 'actions_major', + 'actions_minor', + 'host_uuid', + 'sensorgroup_uuid', + 'suppress', + 'created_at', + 'updated_at'] + + def __init__(self, apiresource): + super(Sensor, self).__init__(apiresource) + + def get_sensor_display_name(self): + if self.sensorname: + return self.sensorname + else: + return '(' + str(self.uuid)[-8:] + ')' + + +def host_sensor_list(request, host_id): + sensors = cgtsclient(request).isensor.list(host_id) + return [Sensor(n) for n in sensors] + + +def host_sensor_get(request, isensor_id): + sensor = cgtsclient(request).isensor.get(isensor_id) + if not sensor: + raise ValueError('No match found for sensor_id "%s".' % isensor_id) + return Sensor(sensor) + + +def host_sensor_create(request, **kwargs): + sensor = cgtsclient(request).isensor.create(**kwargs) + return Sensor(sensor) + + +def host_sensor_update(request, sensor_id, **kwargs): + LOG.debug("sensor_update(): sensor_id=%s, kwargs=%s", sensor_id, kwargs) + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).isensor.update(sensor_id, mypatch) + + +def host_sensor_suppress(request, sensor_id): + kwargs = {'suppress': "True"} + sensor = host_sensor_update(request, sensor_id, **kwargs) + return sensor + + +def host_sensor_unsuppress(request, sensor_id): + kwargs = {'suppress': "False"} + sensor = host_sensor_update(request, sensor_id, **kwargs) + return sensor + + +def host_sensor_delete(request, isensor_id): + return cgtsclient(request).isensor.delete(isensor_id) + + +class SensorGroup(base.APIResourceWrapper): + """Wrapper for Inventory Sensor Groups""" + + _attrs = ['uuid', + 'sensorgroupname', + 'sensortype', + 'state', + 'datatype', + 'sensors', + 'host_uuid', + 'algorithm', + 'actions_critical_group', + 'actions_major_group', + 'actions_minor_group', + 'actions_critical_choices', + 'actions_major_choices', + 'actions_minor_choices', + 'audit_interval_group', + 'suppress', + 'created_at', + 'updated_at'] + + ACTIONS_DISPLAY_CHOICES = ( + (None, _("None")), + (SENSORS_AC_NONE, _("None.")), + (SENSORS_AC_IGNORE, _("Ignore")), + (SENSORS_AC_LOG, _("Log")), + (SENSORS_AC_ALARM, _("Alarm")), + (SENSORS_AC_RESET, _("Reset Host")), + (SENSORS_AC_POWERCYCLE, _("Power Cycle Host")), + (SENSORS_AC_POWEROFF, _("Power Off Host")), + (SENSORS_AC_NOACTIONSCONFIGURABLE, _("No Configurable Actions")), + ) + + def __init__(self, apiresource): + super(SensorGroup, self).__init__(apiresource) + + def get_sensorgroup_display_name(self): + if self.sensorgroupname: + return self.sensorgroupname + else: + return '(' + str(self.uuid)[-8:] + ')' + + @staticmethod + def _get_display_value(display_choices, data): + """Lookup the display value in the provided dictionary.""" + display_value = [display for (value, display) in display_choices + if value and value.lower() == (data or '').lower()] + + if display_value: + return display_value[0] + return None + + def _get_sensorgroup_actions_critical_list(self): + actions_critical_choices_list = [] + if self.actions_critical_choices: + actions_critical_choices_list = \ + self.actions_critical_choices.split(",") + + return actions_critical_choices_list + + @property + def sensorgroup_actions_critical_choices(self): + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, + self.actions_critical_choices) + + actions_critical_choices_tuple = (self.actions_critical_choices, dv) + + return actions_critical_choices_tuple + + @property + def sensorgroup_actions_critical_choices_tuple_list(self): + actions_critical_choices_list = \ + self._get_sensorgroup_actions_critical_list() + + actions_critical_choices_tuple_list = [] + if not actions_critical_choices_list: + ac = SENSORS_AC_NOACTIONSCONFIGURABLE + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + actions_critical_choices_tuple_list.append((ac, dv)) + else: + actions_critical_choices_tuple_set = set() + + ac = SENSORS_AC_IGNORE + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + + actions_critical_choices_tuple_set.add((ac, dv)) + + for ac in actions_critical_choices_list: + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + + if not dv: + dv = ac + + actions_critical_choices_tuple_set.add((ac, dv)) + + actions_critical_choices_tuple_list = \ + list(actions_critical_choices_tuple_set) + + LOG.debug("actions_critical_choices_tuple_list=%s", + actions_critical_choices_tuple_list) + + return actions_critical_choices_tuple_list + + def _get_sensorgroup_actions_major_list(self): + actions_major_choices_list = [] + if self.actions_major_choices: + actions_major_choices_list = \ + self.actions_major_choices.split(",") + + return actions_major_choices_list + + @property + def sensorgroup_actions_major_choices(self): + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, + self.actions_major_choices) + + actions_major_choices_tuple = (self.actions_major_choices, dv) + + return actions_major_choices_tuple + + @property + def sensorgroup_actions_major_choices_tuple_list(self): + actions_major_choices_list = self._get_sensorgroup_actions_major_list() + + actions_major_choices_tuple_list = [] + if not actions_major_choices_list: + ac = SENSORS_AC_NOACTIONSCONFIGURABLE + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + actions_major_choices_tuple_list.append((ac, dv)) + else: + actions_major_choices_tuple_set = set() + + ac = SENSORS_AC_IGNORE + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + + actions_major_choices_tuple_set.add((ac, dv)) + + for ac in actions_major_choices_list: + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + + if not dv: + dv = ac + + actions_major_choices_tuple_set.add((ac, dv)) + + actions_major_choices_tuple_list = \ + list(actions_major_choices_tuple_set) + + LOG.debug("actions_major_choices_tuple_list=%s", + actions_major_choices_tuple_list) + + return actions_major_choices_tuple_list + + def _get_sensorgroup_actions_minor_list(self): + actions_minor_choices_list = [] + if self.actions_minor_choices: + actions_minor_choices_list = \ + self.actions_minor_choices.split(",") + + return actions_minor_choices_list + + @property + def sensorgroup_actions_minor_choices(self): + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, + self.actions_minor_choices) + + actions_minor_choices_tuple = (self.actions_minor_choices, dv) + + return actions_minor_choices_tuple + + @property + def sensorgroup_actions_minor_choices_tuple_list(self): + actions_minor_choices_list = self._get_sensorgroup_actions_minor_list() + + actions_minor_choices_tuple_list = [] + if not actions_minor_choices_list: + ac = SENSORS_AC_NOACTIONSCONFIGURABLE + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + actions_minor_choices_tuple_list.append((ac, dv)) + else: + actions_minor_choices_tuple_set = set() + + ac = SENSORS_AC_IGNORE + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + + actions_minor_choices_tuple_set.add((ac, dv)) + + for ac in actions_minor_choices_list: + dv = self._get_display_value( + self.ACTIONS_DISPLAY_CHOICES, ac) + + if not dv: + dv = ac + + actions_minor_choices_tuple_set.add((ac, dv)) + + actions_minor_choices_tuple_list = \ + list(actions_minor_choices_tuple_set) + + LOG.debug("actions_minor_choices_tuple_list=%s", + actions_minor_choices_tuple_list) + + return actions_minor_choices_tuple_list + + +def host_sensorgroup_list(request, host_id): + sensorgroups = cgtsclient(request).isensorgroup.list(host_id) + return [SensorGroup(n) for n in sensorgroups] + + +def host_sensorgroup_get(request, isensorgroup_id): + sensorgroup = cgtsclient(request).isensorgroup.get(isensorgroup_id) + if not sensorgroup: + raise ValueError('No match found for sensorgroup_id "%s".' % + isensorgroup_id) + return SensorGroup(sensorgroup) + + +def host_sensorgroup_create(request, **kwargs): + sensorgroup = cgtsclient(request).isensorgroup.create(**kwargs) + return SensorGroup(sensorgroup) + + +def host_sensorgroup_update(request, sensorgroup_id, **kwargs): + LOG.debug("sensorgroup_update(): sensorgroup_id=%s, kwargs=%s", + sensorgroup_id, kwargs) + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).isensorgroup.update(sensorgroup_id, mypatch) + + +def host_sensorgroup_delete(request, isensorgroup_id): + return cgtsclient(request).isensorgroup.delete(isensorgroup_id) + + +def host_sensorgroup_relearn(request, host_uuid): + LOG.info("relearn sensor model for host %s", host_uuid) + return cgtsclient(request).isensorgroup.relearn(host_uuid) + + +def host_sensorgroup_suppress(request, sensorgroup_id): + kwargs = {'suppress': "True"} + sensorgroup = host_sensorgroup_update(request, sensorgroup_id, **kwargs) + return sensorgroup + + +def host_sensorgroup_unsuppress(request, sensorgroup_id): + kwargs = {'suppress': "False"} + sensorgroup = host_sensorgroup_update(request, sensorgroup_id, **kwargs) + return sensorgroup + + +class Host(base.APIResourceWrapper): + """Wrapper for Inventory Hosts""" + + _attrs = ['id', 'uuid', 'hostname', 'personality', 'pers_subtype', + 'subfunctions', 'subfunction_oper', 'subfunction_avail', + 'location', 'serialid', 'operational', 'administrative', + 'invprovision', 'peers', + 'availability', 'uptime', 'task', 'capabilities', + 'created_at', 'updated_at', 'mgmt_mac', 'mgmt_ip', + 'bm_ip', 'bm_type', 'bm_username', + 'config_status', 'vim_progress_status', 'patch_current', + 'requires_reboot', 'boot_device', 'rootfs_device', + 'install_output', 'console', 'ttys_dcd', 'patch_state', + 'allow_insvc_patching', 'install_state', 'install_state_info'] + + PERSONALITY_DISPLAY_CHOICES = ( + (PERSONALITY_CONTROLLER, _("Controller")), + (PERSONALITY_COMPUTE, _("Compute")), + (PERSONALITY_NETWORK, _("Network")), + (PERSONALITY_STORAGE, _("Storage")), + ) + ADMIN_DISPLAY_CHOICES = ( + ('locked', _("Locked")), + ('unlocked', _("Unlocked")), + ) + OPER_DISPLAY_CHOICES = ( + ('disabled', _("Disabled")), + ('enabled', _("Enabled")), + ) + AVAIL_DISPLAY_CHOICES = ( + ('available', _("Available")), + ('intest', _("In-Test")), + ('degraded', _("Degraded")), + ('failed', _("Failed")), + ('power-off', _("Powered-Off")), + ('offline', _("Offline")), + ('online', _("Online")), + ('offduty', _("Offduty")), + ('dependency', _("Dependency")), + ) + CONFIG_STATUS_DISPLAY_CHOICES = ( + ('up_to_date', _("up-to-date")), + ('out_of_date', _("out-of-date")), + ) + PATCH_STATE_DISPLAY_CHOICES = ( + (patch_constants.PATCH_AGENT_STATE_IDLE, + _("Idle")), + (patch_constants.PATCH_AGENT_STATE_INSTALLING, + _("Patch Installing")), + (patch_constants.PATCH_AGENT_STATE_INSTALL_FAILED, + _("Patch Install Failed")), + (patch_constants.PATCH_AGENT_STATE_INSTALL_REJECTED, + _("Patch Install Rejected")), + ) + + INSTALL_STATE_DISPLAY_CHOICES = ( + (constants.INSTALL_STATE_PRE_INSTALL, _("Pre-install")), + (constants.INSTALL_STATE_INSTALLING, _("Installing Packages")), + (constants.INSTALL_STATE_POST_INSTALL, _("Post-install")), + (constants.INSTALL_STATE_FAILED, _("Install Failed")), + (constants.INSTALL_STATE_INSTALLED, _("Installed")), + (constants.INSTALL_STATE_BOOTING, _("Booting")), + (constants.INSTALL_STATE_COMPLETED, _("Completed")), + ) + + SUBTYPE_CHOICES = ( + (PERSONALITY_SUBTYPE_CEPH_BACKING, _("CEPH backing")), + (PERSONALITY_SUBTYPE_CEPH_CACHING, _("CEPH caching")), + ) + + def __init__(self, apiresource): + super(Host, self).__init__(apiresource) + self._personality = self.personality + self._subfunctions = self.subfunctions + self._subfunction_oper = self.subfunction_oper + self._subfunction_avail = self.subfunction_avail + self._location = self.location + self._peers = self.peers + self._bm_type = self.bm_type + self._administrative = self.administrative + self._invprovision = self.invprovision + self._operational = self.operational + self._availability = self.availability + self._capabilities = self.capabilities + self._ttys_dcd = self.ttys_dcd + self._pers_subtype = self.capabilities.get('pers_subtype', "") + self.patch_current = "N/A" + self.requires_reboot = "N/A" + self.allow_insvc_patching = True + self._patch_state = patch_constants.PATCH_AGENT_STATE_IDLE + + self._install_state = self.install_state + if self._install_state is not None: + self._install_state = self._install_state.strip("+") + + @property + def personality(self): + # Override controller personality to retrieve + # the current activity state which + # is reported in the hosts location field + if (self._personality == PERSONALITY_CONTROLLER): + if (self._capabilities['Personality'] == 'Controller-Active'): + return _('Controller-Active') + else: + return _('Controller-Standby') + return self._get_display_value(self.PERSONALITY_DISPLAY_CHOICES, + self._personality) + + @property + def additional_subfunctions(self): + return len(self._subfunctions.split(',')) > 1 + + @property + def is_cpe(self): + subfunctions = self._subfunctions.split(',') + if PERSONALITY_CONTROLLER in subfunctions and \ + PERSONALITY_COMPUTE in subfunctions: + return True + else: + return False + + @property + def subfunctions(self): + return self._subfunctions.split(',') + + @property + def subfunction_oper(self): + return self._get_display_value(self.OPER_DISPLAY_CHOICES, + self._subfunction_oper) + + @property + def subfunction_avail(self): + return self._get_display_value(self.AVAIL_DISPLAY_CHOICES, + self._subfunction_avail) + + @property + def compute_config_required(self): + return self.config_status == 'Compute config required' + + @property + def location(self): + if hasattr(self._location, 'locn'): + return self._location.locn + if 'locn' in self._location: + return self._location['locn'] + else: + return '' + + @property + def peers(self): + if hasattr(self._peers, 'name'): + return self._peers.name + if self._peers and 'name' in self._peers: + return self._peers['name'] + else: + return '' + + @property + def boottime(self): + return datetime.datetime.now() - datetime.timedelta( + seconds=self.uptime) + + @property + def administrative(self): + return self._get_display_value(self.ADMIN_DISPLAY_CHOICES, + self._administrative) + + @property + def operational(self): + return self._get_display_value(self.OPER_DISPLAY_CHOICES, + self._operational) + + @property + def availability(self): + return self._get_display_value(self.AVAIL_DISPLAY_CHOICES, + self._availability) + + @property + def bm_type(self): + bm_type = self._bm_type + if bm_type and not bm_type.lower().startswith(BM_TYPE_NONE): + bm_type = BM_TYPE_GENERIC + else: + bm_type = BM_TYPE_NULL + + return bm_type + + @property + def ttys_dcd(self): + return self._ttys_dcd == 'True' + + @property + def patch_state(self): + return self._get_display_value(self.PATCH_STATE_DISPLAY_CHOICES, + self._patch_state) + + @property + def pers_subtype(self): + return self._get_display_value(self.SUBTYPE_CHOICES, + self._pers_subtype) + + @property + def install_state(self): + return self._get_display_value(self.INSTALL_STATE_DISPLAY_CHOICES, + self._install_state) + + def _get_display_value(self, display_choices, data): + """Lookup the display value in the provided dictionary.""" + display_value = [display for (value, display) in display_choices + if value.lower() == (data or '').lower()] + + if display_value: + return display_value[0] + return None + + +class AlarmSummary(base.APIResourceWrapper): + """Wrapper for Inventory Alarm Summaries""" + + _attrs = ['system_uuid', + 'warnings', + 'minor', + 'major', + 'critical', + 'status'] + + def __init__(self, apiresource): + super(AlarmSummary, self).__init__(apiresource) + + +def alarm_summary_get(request, include_suppress=False): + summary = cgtsclient(request).ialarm.summary( + include_suppress=include_suppress) + if len(summary) > 0: + return AlarmSummary(summary[0]) + return None + + +class Alarm(base.APIResourceWrapper): + """Wrapper for Inventory Alarms""" + + _attrs = ['uuid', + 'alarm_id', + 'alarm_state', + 'entity_type_id', + 'entity_instance_id', + 'timestamp', + 'severity', + 'reason_text', + 'alarm_type', + 'probable_cause', + 'proposed_repair_action', + 'service_affecting', + 'suppression_status', + 'mgmt_affecting'] + + def __init__(self, apiresource): + super(Alarm, self).__init__(apiresource) + + +def alarm_list(request, search_opts=None): + paginate = False + include_suppress = False + + if search_opts is None: + search_opts = {} + + limit = search_opts.get('limit', None) + marker = search_opts.get('marker', None) + sort_key = search_opts.get('sort_key', None) + sort_dir = search_opts.get('sort_dir', None) + page_size = base.get_request_page_size(request, limit) + + if "suppression" in search_opts: + suppression = search_opts.pop('suppression') + + if suppression == FM_SUPPRESS_SHOW: + include_suppress = True + elif suppression == FM_SUPPRESS_HIDE: + include_suppress = False + + if 'paginate' in search_opts: + paginate = search_opts.pop('paginate') + if paginate: + limit = page_size + 1 + + alarms = cgtsclient(request).ialarm.list( + limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir, + include_suppress=include_suppress) + + has_more_data = False + if paginate and len(alarms) > page_size: + alarms.pop(-1) + has_more_data = True + elif paginate and len(alarms) > getattr(settings, + 'API_RESULT_LIMIT', 1000): + has_more_data = True + + if paginate: + return [Alarm(n) for n in alarms], has_more_data + else: + return [Alarm(n) for n in alarms] + + +def alarm_get(request, alarm_id): + alarm = cgtsclient(request).ialarm.get(alarm_id) + if not alarm: + raise ValueError('No match found for alarm_id "%s".' % alarm_id) + return Alarm(alarm) + + +def system_list(request): + systems = cgtsclient(request).isystem.list() + return [System(n) for n in systems] + + +def system_get(request): + system = cgtsclient(request).isystem.list()[0] + if not system: + raise ValueError('No system found.') + return System(system) + + +def system_update(request, system_id, **kwargs): + LOG.debug("system_update(): system_id=%s, kwargs=%s", system_id, kwargs) + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).isystem.update(system_id, mypatch) + + +def host_create(request, **kwargs): + LOG.debug("host_create(): kwargs=%s", kwargs) + host = cgtsclient(request).ihost.create(**kwargs) + return Host(host) + + +def host_update(request, host_id, **kwargs): + LOG.debug("host_update(): host_id=%s, kwargs=%s", host_id, kwargs) + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).ihost.update(host_id, mypatch) + + +def host_apply_profile(request, host_id, profile_uuid): + LOG.debug("host_apply_profile(): host_id=%s, profile_uuid=%s", + host_id, profile_uuid) + + kwargs = {} + kwargs['action'] = 'apply-profile' + kwargs['iprofile_uuid'] = profile_uuid + + host = host_update(request, host_id, **kwargs) + return host + + +def host_delete(request, host_id): + LOG.debug("host_delete(): host_id=%s", host_id) + return cgtsclient(request).ihost.delete(host_id) + + +def host_lock(request, host_id): + kwargs = {'action': 'lock'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_force_lock(request, host_id): + kwargs = {'action': 'force-lock'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_unlock(request, host_id): + kwargs = {'action': 'unlock'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_force_unlock(request, host_id): + kwargs = {'action': 'force-unlock'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_reboot(request, host_id): + kwargs = {'action': 'reboot'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_reset(request, host_id): + kwargs = {'action': 'reset'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_reinstall(request, host_id): + kwargs = {'action': 'reinstall'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_power_on(request, host_id): + kwargs = {'action': 'power-on'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_power_off(request, host_id): + kwargs = {'action': 'power-off'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_swact(request, host_id): + kwargs = {'action': 'swact'} + host = host_update(request, host_id, **kwargs) + return host + + +def host_get(request, host_id): + host = cgtsclient(request).ihost.get(host_id) + if not host: + raise ValueError('No match found for host_id "%s".' % host_id) + return Host(host) + + +def host_list(request): + hosts = cgtsclient(request).ihost.list() + return [Host(n) for n in hosts] + + +class DNS(base.APIResourceWrapper): + """...""" + + _attrs = ['isystem_uuid', 'nameservers', 'uuid', 'link'] + + def __init__(self, apiresource): + super(DNS, self).__init__(apiresource) + + +def dns_update(request, dns_id, **kwargs): + LOG.debug("dns_update(): dns_id=%s, kwargs=%s", dns_id, kwargs) + mypatch = [] + + for key, value in kwargs.iteritems(): + if key == 'nameservers' and not value: + value = 'NC' + mypatch.append(dict(path='/' + key, value=value, op='replace')) + + return cgtsclient(request).idns.update(dns_id, mypatch) + + +def dns_delete(request, dns_id): + LOG.debug("dns_delete(): dns_id=%s", dns_id) + return cgtsclient(request).idns.delete(dns_id) + + +def dns_get(request, dns_id): + dns = cgtsclient(request).idns.get(dns_id) + if not dns: + raise ValueError('No match found for dns_id "%s".' % dns_id) + return DNS(dns) + + +def dns_list(request): + dns = cgtsclient(request).idns.list() + return [DNS(n) for n in dns] + + +class NTP(base.APIResourceWrapper): + """...""" + + _attrs = ['isystem_uuid', 'ntpservers', 'uuid', 'link'] + + def __init__(self, apiresource): + super(NTP, self).__init__(apiresource) + + +def ntp_update(request, ntp_id, **kwargs): + LOG.debug("ntp_update(): ntp_id=%s, kwargs=%s", ntp_id, kwargs) + mypatch = [] + + for key, value in kwargs.iteritems(): + if key == 'ntpservers' and not value: + value = 'NC' + mypatch.append(dict(path='/' + key, value=value, op='replace')) + + return cgtsclient(request).intp.update(ntp_id, mypatch) + + +def ntp_delete(request, ntp_id): + LOG.debug("ntp_delete(): ntp_id=%s", ntp_id) + return cgtsclient(request).intp.delete(ntp_id) + + +def ntp_get(request, ntp_id): + ntp = cgtsclient(request).intp.get(ntp_id) + if not ntp: + raise ValueError('No match found for ntp_id "%s".' % ntp_id) + return NTP(ntp) + + +def ntp_list(request): + ntp = cgtsclient(request).intp.list() + return [NTP(n) for n in ntp] + + +class EXTOAM(base.APIResourceWrapper): + """...""" + + _attrs = ['isystem_uuid', 'oam_subnet', 'oam_gateway_ip', + 'oam_floating_ip', 'oam_c0_ip', 'oam_c1_ip', + 'oam_start_ip', 'oam_end_ip', + 'uuid', 'link', 'region_config'] + + def __init__(self, apiresource): + super(EXTOAM, self).__init__(apiresource) + + if hasattr(self, 'uuid'): + self._oam_subnet = self.oam_subnet + self._oam_gateway_ip = self.oam_gateway_ip + self._oam_floating_ip = self.oam_floating_ip + self._oam_c0_ip = self.oam_c0_ip + self._oam_c1_ip = self.oam_c1_ip + + self._region_config = self.region_config + self._oam_start_ip = self.oam_start_ip or "" + self._oam_end_ip = self.oam_end_ip or "" + else: + self._oam_subnet = None + self._oam_gateway_ip = None + self._oam_floating_ip = None + self._oam_c0_ip = None + self._oam_c1_ip = None + + self._region_config = None + self._oam_start_ip = None + self._oam_end_ip = None + + @property + def oam_subnet(self): + return self._oam_subnet + + @property + def oam_gateway_ip(self): + return self._oam_gateway_ip + + @property + def oam_floating_ip(self): + return self._oam_floating_ip + + @property + def oam_c0_ip(self): + return self._oam_c0_ip + + @property + def oam_c1_ip(self): + return self._oam_c1_ip + + @property + def region_config(self): + return self._region_config + + @property + def oam_start_ip(self): + return self._oam_start_ip or "" + + @property + def oam_end_ip(self): + return self._oam_end_ip or "" + + +def extoam_update(request, extoam_id, **kwargs): + LOG.debug("extoam_update(): extoam_id=%s, kwargs=%s", extoam_id, kwargs) + # print 'THIS IS IN SYSINV UPDATE: ', kwargs + mypatch = [] + # print "\nThis is the dns_id: ", dns_id, "\n" + # print "\nThese are the values in sysinv dns_update: ", kwargs, "\n" + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).iextoam.update(extoam_id, mypatch) + + +def extoam_delete(request, extoam_id): + LOG.debug("extoam_delete(): extoam_id=%s", extoam_id) + return cgtsclient(request).iextoam.delete(extoam_id) + + +def extoam_get(request, extoam_id): + extoam = cgtsclient(request).iextoam.get(extoam_id) + # print "THIS IS SYSNINV GET" + if not extoam: + raise ValueError('No match found for extoam_id "%s".' % extoam_id) + return EXTOAM(extoam) + + +def extoam_list(request): + extoam = cgtsclient(request).iextoam.list() + # print "THIS IS SYSINV LIST" + return [EXTOAM(n) for n in extoam] + + +class StorageCeph(base.APIResourceWrapper): + """...""" + + _attrs = ['cinder_pool_gib', 'glance_pool_gib', 'ephemeral_pool_gib', + 'object_pool_gib', 'object_gateway', 'uuid', 'link', + 'ceph_total_space_gib'] + + def __init__(self, apiresource): + super(StorageCeph, self).__init__(apiresource) + + if hasattr(self, 'uuid'): + self._cinder_pool_gib = self.cinder_pool_gib + self._glance_pool_gib = self.glance_pool_gib + self._ephemeral_pool_gib = self.ephemeral_pool_gib + self._object_pool_gib = self.object_pool_gib + self._object_gateway = self.object_gateway + self._ceph_total_space_gib = self.ceph_total_space_gib + else: + self._cinder_pool_gib = None + self._glance_pool_gib = None + self._ephemeral_pool_gib = None + self._object_pool_gib = None + self._object_gateway = None + self._ceph_total_space_gib = None + + @property + def cinder_pool_gib(self): + return self._cinder_pool_gib + + @property + def glance_pool_gib(self): + return self._glance_pool_gib + + @property + def ephemeral_pool_gib(self): + return self._ephemeral_pool_gib + + @property + def object_pool_gib(self): + return self._object_pool_gib + + @property + def object_gateway(self): + return self._object_gateway + + @property + def ceph_total_space_gib(self): + return self._ceph_total_space_gib + + +class StorageBackend(base.APIResourceWrapper): + """...""" + _attrs = ['isystem_uuid', 'backend', 'state', 'task', 'uuid', 'link'] + + def __init__(self, apiresource): + super(StorageBackend, self).__init__(apiresource) + + if hasattr(self, 'uuid'): + self._backend = self.backend + self._state = self.state + self._task = self.task + else: + self._backend = None + self._state = None + self._task = None + + @property + def backend(self): + return self._backend + + @property + def state(self): + return self._state + + @property + def task(self): + return self._task + + +class ControllerFS(base.APIResourceWrapper): + """...""" + _attrs = ['uuid', 'link', 'name', 'size', 'logical_volume', 'replicated', + 'device_path', 'ceph_mon_gib', 'hostname'] + + def __init__(self, apiresource): + super(ControllerFS, self).__init__(apiresource) + + if hasattr(self, 'ceph_mon_gib'): + self._size = self.ceph_mon_gib + self._name = 'ceph-mon' + self._logical_volume = None + self._replicated = None + self._uuid = self.uuid + + else: + self._uuid = self.uuid + self._name = self.name + self._logical_volume = self.logical_volume + self._size = self.size + self._replicated = self.replicated + + @property + def uuid(self): + return self._uuid + + @property + def name(self): + return self._name + + @property + def logical_volume(self): + return self._logical_volume + + @property + def size(self): + return self._size + + @property + def replicated(self): + return self._replicated + + +class CephMon(base.APIResourceWrapper): + """...""" + _attrs = ['device_path', 'ceph_mon_gib', 'hostname', 'uuid', 'link'] + + def __init__(self, apiresource): + super(CephMon, self).__init__(apiresource) + + if hasattr(self, 'uuid'): + self._device_path = self.device_path + self._ceph_mon_gib = self.ceph_mon_gib + self._hostname = self.hostname + else: + self._device_path = None + self._ceph_mon_gib = None + self._hostname = None + + @property + def device_path(self): + return self._device_path + + @property + def ceph_mon_gib(self): + return self._ceph_mon_gib + + @property + def hostname(self): + return self._hostname + + +class STORAGE(base.APIResourceWrapper): + """...""" + _attrs = ['isystem_uuid', 'backup_gib', 'scratch_gib', 'cgcs_gib', + 'img_conversions_gib', 'database_gib', + 'uuid', 'link', 'backend', 'glance_backend', + 'cinder_pool_gib', 'glance_pool_gib', 'ephemeral_pool_gib', + 'object_pool_gib', 'ceph_mon_gib', 'ceph_total_space_gib'] + + def __init__(self, controller_fs, ceph_mon, storage_ceph): + if controller_fs: + super(STORAGE, self).__init__(controller_fs) + elif storage_ceph: + super(STORAGE, self).__init__(storage_ceph) + + self._backup_gib = None + self._scratch_gib = None + self._cgcs_gib = None + self._img_conversions_gib = None + self._database_gib = None + self._backend = None + self._cinder_pool_gib = None + self._glance_pool_gib = None + self._ephemeral_pool_gib = None + self._ceph_mon_gib = None + self._ceph_total_space_gib = None + + if hasattr(self, 'uuid'): + if storage_ceph: + self._glance_pool_gib = storage_ceph.glance_pool_gib + self._ephemeral_pool_gib = storage_ceph.ephemeral_pool_gib + self._cinder_pool_gib = storage_ceph.cinder_pool_gib + self._object_pool_gib = storage_ceph.object_pool_gib + self._ceph_total_space_gib = storage_ceph.ceph_total_space_gib + + if controller_fs: + self._backup_gib = controller_fs.backup_gib + self._scratch_gib = controller_fs.scratch_gib + self._cgcs_gib = controller_fs.cgcs_gib + self._img_conversions_gib = controller_fs.img_conversions_gib + self._database_gib = controller_fs.database_gib + + if ceph_mon: + self._device_path = ceph_mon.device_path + self._ceph_mon_gib = ceph_mon.ceph_mon_gib + self._hostname = ceph_mon.hostname + + @property + def backup_gib(self): + return self._backup_gib + + @property + def scratch_gib(self): + return self._scratch_gib + + @property + def cgcs_gib(self): + return self._cgcs_gib + + @property + def database_gib(self): + return self._database_gib + + @property + def img_conversions_gib(self): + return self._img_conversions_gib + + @property + def backend(self): + return self._backend + + @property + def glance_backend(self): + return self._glance_backend + + @property + def cinder_pool_gib(self): + return self._cinder_pool_gib + + @property + def glance_pool_gib(self): + return self._glance_pool_gib + + @property + def ephemeral_pool_gib(self): + return self._ephemeral_pool_gib + + @property + def ceph_mon_gib(self): + return self._ceph_mon_gib + + @property + def ceph_total_space_gib(self): + return self._ceph_total_space_gib + + +def storfs_update(request, controller_fs_id, **kwargs): + LOG.info("Updating controller fs storage with kwargs=%s", kwargs) + + my_patch = [] + + for key, value in kwargs.iteritems(): + my_patch.append(dict(path='/' + key, value=value, + op='replace')) + + return cgtsclient(request).controller_fs.update(controller_fs_id, my_patch) + + +def storfs_update_many(request, system_uuid, **kwargs): + LOG.info("Updating controller fs storage with kwargs=%s", kwargs) + + patch_list = [] + + for key, value in kwargs.iteritems(): + patch = [] + patch.append({'op': 'replace', 'path': '/name', 'value': key}) + patch.append({'op': 'replace', 'path': '/size', 'value': value}) + patch_list.append(patch) + + return cgtsclient(request).controller_fs.update_many(system_uuid, + patch_list) + + +def ceph_mon_update(request, ceph_mon_id, **kwargs): + LOG.info("Updating ceph-mon storage with kwargs=%s", kwargs) + + my_patch = [] + + for key, value in kwargs.iteritems(): + my_patch.append(dict(path='/' + key, value=value, + op='replace')) + + return cgtsclient(request).ceph_mon.update(ceph_mon_id, my_patch) + + +def storpool_update(request, storage_ceph_id, **kwargs): + LOG.info("Updating storage pool with kwargs=%s", kwargs) + + my_patch = [] + + for key, value in kwargs.iteritems(): + my_patch.append(dict(path='/' + key, value=value, + op='replace')) + + return cgtsclient(request).storage_ceph.update(storage_ceph_id, + my_patch) + + +def controllerfs_get(request, name): + fs_list = controllerfs_list(request) + for controller_fs in fs_list: + if controller_fs.name == name: + return ControllerFS(controller_fs) + + raise ValueError( + 'No match found for filesystem with name "%s".' % name) + + +def cephmon_get(request, host_id=None): + cephmon = cgtsclient(request).ceph_mon.list(host_id) + if not cephmon: + return None + return CephMon(cephmon[0]) + + +def storagepool_get(request, storceph_id=None): + storceph = cgtsclient(request).storage_ceph.get(storceph_id) + if not storceph: + return None + return StorageCeph(storceph) + + +def cephmon_list(request): + ceph_mons = cgtsclient(request).ceph_mon.list() + if not ceph_mons: + return None + return [CephMon(n) for n in ceph_mons] + + +def storagepool_list(request): + storage_pools = cgtsclient(request).storage_ceph.list() + if not storage_pools: + return None + return [StorageCeph(n) for n in storage_pools] + + +def storagefs_list(request): + # Obtain the storage data from controller_fs and ceph_mon. + ceph_mon_list = cgtsclient(request).ceph_mon.list() + + # Verify if the results are None and if not, extract the first object. + # - controller_fs is a one row tables, so the first + # element of the list is also the only one. + # - ceph_mon has the ceph_mon_gib field identical for all the entries, + # so the first element is enough for obtaining the needed data. + + controllerfs_obj = None + ceph_mon_obj = None + + if ceph_mon_list: + ceph_mon_obj = ceph_mon_list[0] + + return [STORAGE(controllerfs_obj, ceph_mon_obj, None)] + + +def controllerfs_list(request): + controllerfs = cgtsclient(request).controller_fs.list() + ceph_mon_list = cgtsclient(request).ceph_mon.list() + + if ceph_mon_list and not is_system_mode_simplex(request): + controllerfs.append(ceph_mon_list[0]) + + return [ControllerFS(n) for n in controllerfs] + + +def storage_backend_list(request): + backends = cgtsclient(request).storage_backend.list() + + return [StorageBackend(n) for n in backends] + + +def storage_usage_list(request): + ulist = cgtsclient(request).storage_backend.usage() + return ulist + + +def get_cinder_backend(request): + storage_list = storage_backend_list(request) + cinder_backends = [] + + if storage_list: + for storage in storage_list: + if hasattr(storage, 'backend'): + cinder_backends.append(storage.backend) + + return cinder_backends + + +def host_node_list(request, host_id): + nodes = cgtsclient(request).inode.list(host_id) + return [Node(n) for n in nodes] + + +def host_node_get(request, node_id): + node = cgtsclient(request).inode.get(node_id) + if not node: + raise ValueError('No match found for node_id "%s".' % node_id) + return Node(node) + + +def host_cpu_list(request, host_id): + cpus = cgtsclient(request).icpu.list(host_id) + return [Cpu(n) for n in cpus] + + +def _update_cpu_capability(cpu_data): + capability = {'function': cpu_data.get('function')} + sockets = [] + for k, v in cpu_data.items(): + if k.startswith('num_cores_on_processor'): + sockets.append({k.strip('num_cores_on_processor'): v}) + + capability.update({'sockets': sockets}) + LOG.info("_update_cpu_capability=%s", capability) + return capability + + +def host_cpus_modify(request, host_uuid, + platform_cpu_data, + vswitch_cpu_data, + shared_cpu_data): + + capabilities = [] + if platform_cpu_data: + capability = _update_cpu_capability(platform_cpu_data) + capabilities.append(capability) + if vswitch_cpu_data: + capability = _update_cpu_capability(vswitch_cpu_data) + capabilities.append(capability) + if shared_cpu_data: + capability = _update_cpu_capability(shared_cpu_data) + capabilities.append(capability) + + LOG.info("host_cpus_modify host_uuid=%s capabilities=%s", + host_uuid, capabilities) + + return cgtsclient(request).ihost.host_cpus_modify(host_uuid, capabilities) + + +def host_cpu_update(request, cpu_id, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).icpu.update(cpu_id, mypatch) + + +def host_memory_list(request, host_id): + memorys = cgtsclient(request).imemory.list(host_id) + return [Memory(n) for n in memorys] + + +def host_memory_get(request, memory_id): + memory = cgtsclient(request).imemory.get(memory_id) + if not memory: + raise ValueError('No match found for memory_id "%s".' % memory_id) + return Memory(memory) + + +def host_memory_update(request, memory_id, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).imemory.update(memory_id, mypatch) + + +def host_port_list(request, host_id): + ports = cgtsclient(request).ethernet_port.list(host_id) + return [Port(n) for n in ports] + + +def host_port_get(request, port_id): + port = cgtsclient(request).ethernet_port.get(port_id) + if not port: + raise ValueError('No match found for port_id "%s".' % port_id) + return Port(port) + + +def host_port_update(request, port_id, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).ethernet_port.update(port_id, mypatch) + + +def host_disk_list(request, host_id): + disks = cgtsclient(request).idisk.list(host_id) + return [Disk(n) for n in disks] + + +def host_disk_get(request, disk_id): + disk = cgtsclient(request).idisk.get(disk_id) + if not disk: + raise ValueError('No match found for disk_id "%s".' % disk_id) + return Disk(disk) + + +def host_stor_list(request, host_id): + volumes = cgtsclient(request).istor.list(host_id) + return [StorageVolume(n) for n in volumes] + + +def host_stor_get(request, stor_id): + volume = cgtsclient(request).istor.get(stor_id) + if not volume: + raise ValueError('No match found for stor_id "%s".' % stor_id) + return StorageVolume(volume) + + +def host_stor_create(request, **kwargs): + stor = cgtsclient(request).istor.create(**kwargs) + return StorageVolume(stor) + + +def host_stor_delete(request, stor_id): + return cgtsclient(request).istor.delete(stor_id) + + +def host_stor_update(request, stor_id, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + + stor = cgtsclient(request).istor.update(stor_id, mypatch) + return StorageVolume(stor) + + +def host_stor_get_by_function(request, host_id, function=None): + volumes = cgtsclient(request).istor.list(host_id) + + if function: + volumes = [v for v in volumes if v.function == function] + + return [StorageVolume(n) for n in volumes] + + +class Interface(base.APIResourceWrapper): + """Wrapper for Inventory Interfaces""" + + _attrs = ['id', 'uuid', 'ifname', 'iftype', 'imtu', 'imac', 'networktype', + 'aemode', 'txhashpolicy', 'vlan_id', 'uses', 'used_by', + 'ihost_uuid', 'providernetworks', + 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool', + 'sriov_numvfs'] + + def __init__(self, apiresource): + super(Interface, self).__init__(apiresource) + if not self.ifname: + self.ifname = '(' + str(self.uuid)[-8:] + ')' + + +def host_interface_list(request, host_id): + interfaces = cgtsclient(request).iinterface.list(host_id) + return [Interface(n) for n in interfaces] + + +def host_interface_get(request, interface_id): + interface = cgtsclient(request).iinterface.get(interface_id) + if not interface: + raise ValueError( + 'No match found for interface_id "%s".' % interface_id) + return Interface(interface) + + +def host_interface_create(request, **kwargs): + interface = cgtsclient(request).iinterface.create(**kwargs) + return Interface(interface) + + +def host_interface_update(request, interface_id, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).iinterface.update(interface_id, mypatch) + + +def host_interface_delete(request, interface_id): + return cgtsclient(request).iinterface.delete(interface_id) + + +class Address(base.APIResourceWrapper): + """Wrapper for Inventory Addresses""" + + _attrs = ['uuid', 'interface_uuid', 'networktype', 'address', 'prefix', + 'enable_dad'] + + def __init__(self, apiresource): + super(Address, self).__init__(apiresource) + + +def address_list_by_interface(request, interface_id): + addresses = cgtsclient(request).address.list_by_interface(interface_id) + return [Address(n) for n in addresses] + + +def address_get(request, address_uuid): + address = cgtsclient(request).address.get(address_uuid) + if not address: + raise ValueError( + 'No match found for address uuid "%s".' % address_uuid) + return Address(address) + + +def address_create(request, **kwargs): + address = cgtsclient(request).address.create(**kwargs) + return Address(address) + + +def address_delete(request, address_uuid): + return cgtsclient(request).address.delete(address_uuid) + + +class AddressPool(base.APIResourceWrapper): + """Wrapper for Inventory Address Pools""" + + _attrs = ['uuid', 'name', 'network', 'prefix', 'order', 'ranges'] + + def __init__(self, apiresource): + super(AddressPool, self).__init__(apiresource) + + +def address_pool_list(request): + pools = cgtsclient(request).address_pool.list() + return [AddressPool(p) for p in pools] + + +def address_pool_get(request, address_pool_uuid): + pool = cgtsclient(request).address_pool.get(address_pool_uuid) + if not pool: + raise ValueError( + 'No match found for address pool uuid "%s".' % address_pool_uuid) + return AddressPool(pool) + + +def address_pool_create(request, **kwargs): + pool = cgtsclient(request).address_pool.create(**kwargs) + return AddressPool(pool) + + +def address_pool_delete(request, address_pool_uuid): + return cgtsclient(request).address_pool.delete(address_pool_uuid) + + +def address_pool_update(request, address_pool_uuid, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).address_pool.update(address_pool_uuid, mypatch) + + +class Route(base.APIResourceWrapper): + """Wrapper for Inventory Routers""" + + _attrs = ['uuid', 'interface_uuid', 'network', + 'prefix', 'gateway', 'metric'] + + def __init__(self, apiresource): + super(Route, self).__init__(apiresource) + + +def route_list_by_interface(request, interface_id): + routees = cgtsclient(request).route.list_by_interface(interface_id) + return [Route(n) for n in routees] + + +def route_get(request, route_uuid): + route = cgtsclient(request).route.get(route_uuid) + if not route: + raise ValueError( + 'No match found for route uuid "%s".' % route_uuid) + return Route(route) + + +def route_create(request, **kwargs): + route = cgtsclient(request).route.create(**kwargs) + return Route(route) + + +def route_delete(request, route_uuid): + return cgtsclient(request).route.delete(route_uuid) + + +class BaseProfile(base.APIResourceWrapper): + """Base Wrapper class for Profiles""" + + def __init__(self, request, apiresource): + super(BaseProfile, self).__init__(apiresource) + # load dependent data if required + self._load_data(request) + + def _load_data(self, request): + """stub function for loading additional data""" + pass + + +class InterfaceProfile(BaseProfile): + """Wrapper for Inventory Interface Profile""" + + _attrs = ['uuid', 'profilename', 'ports', 'interfaces'] + + def __init__(self, request, apiresource): + super(InterfaceProfile, self).__init__(request, apiresource) + + self.ports = [Port(n) for n in self.ports] + self.interfaces = [Interface(n) for n in self.interfaces] + + for p in self.ports: + p.namedisplay = p.get_port_display_name() + + for i in self.interfaces: + i.ports = [p.get_port_display_name() for p in self.ports if + p.interface_uuid and p.interface_uuid == i.uuid] + i.ports = ", ".join(i.ports) + + +class InterfaceProfileFragmentary(InterfaceProfile): + def _load_data(self, request): + self.ports = \ + cgtsclient(request).iprofile.list_ethernet_port(self.uuid) + self.interfaces = \ + cgtsclient(request).iprofile.list_iinterface(self.uuid) + + +def host_interfaceprofile_list(request): + profiles = cgtsclient(request).iprofile.list_interface_profiles() + return [InterfaceProfile(request, n) for n in profiles] + + +def host_interfaceprofile_create(request, **kwargs): + host_id = kwargs['host_id'] + del kwargs['host_id'] + + ihost = cgtsclient(request).ihost.get(host_id) + kwargs['ihost_uuid'] = ihost.uuid + + kwargs['profiletype'] = 'if' + + # create new if profile + iprofile = cgtsclient(request).iprofile.create(**kwargs) + return InterfaceProfileFragmentary(request, iprofile) + + +def host_interfaceprofile_delete(request, iprofile_uuid): + return cgtsclient(request).iprofile.delete(iprofile_uuid) + + +class CpuProfile(BaseProfile): + """Wrapper for Inventory Cpu Profiles""" + + _attrs = ['uuid', 'profilename', 'cpus', 'nodes'] + + def __init__(self, request, apiresource): + super(CpuProfile, self).__init__(request, apiresource) + + self.cpus = [Cpu(n) for n in self.cpus] + self.nodes = [Node(n) for n in self.nodes] + + icpu_utils.restructure_host_cpu_data(self) + + +class CpuProfileFragmentary(CpuProfile): + def _load_data(self, request): + self.cpus = cgtsclient(request).iprofile.list_icpus(self.uuid) + self.nodes = cgtsclient(request).iprofile.list_inodes(self.uuid) + + +def host_cpuprofile_list(request): + profiles = cgtsclient(request).iprofile.list_cpu_profiles() + return [CpuProfile(request, n) for n in profiles] + + +def host_cpuprofile_create(request, **kwargs): + host_id = kwargs['host_id'] + del kwargs['host_id'] + + ihost = cgtsclient(request).ihost.get(host_id) + kwargs['ihost_uuid'] = ihost.uuid + + kwargs['profiletype'] = 'cpu' + + # create new cpu profile + iprofile = cgtsclient(request).iprofile.create(**kwargs) + return CpuProfileFragmentary(request, iprofile) + + +def host_cpuprofile_delete(request, iprofile_uuid): + return cgtsclient(request).iprofile.delete(iprofile_uuid) + + +# +# DISK PROFILES +# + +# Most of the work happens in the cgts-client package +# to avoid code duplicated in GUI and CLI + +class DiskProfile(BaseProfile): + """Wrapper for Inventory Disk Profiles""" + + _attrs = ['uuid', 'profilename', 'disks', 'partitions', 'stors', 'lvgs', + 'pvs'] + + def __init__(self, request, apiresource): + super(DiskProfile, self).__init__(request, apiresource) + + self.disks = [Disk(n) for n in self.disks] + self.partitions = [Partition(n) for n in self.partitions] + self.stors = [StorageVolume(n) for n in self.stors] + self.pvs = [PhysicalVolume(n) for n in self.pvs] + self.lvgs = [LocalVolumeGroup(n) for n in self.lvgs] + + for lvg in self.lvgs: + lvg.params = host_lvg_get_params(request, lvg.uuid, False, lvg) + + +class DiskProfileFragmentary(DiskProfile): + def _load_data(self, request): + self.disks = cgtsclient(request).iprofile.list_idisks(self.uuid) + self.partitions = cgtsclient(request).iprofile.list_partitions( + self.uuid) + self.stors = cgtsclient(request).iprofile.list_istors(self.uuid) + self.pvs = cgtsclient(request).iprofile.list_ipvs(self.uuid) + self.lvgs = cgtsclient(request).iprofile.list_ilvgs(self.uuid) + + +def host_diskprofile_list(request): + profiles = cgtsclient(request).iprofile.list_storage_profiles() + return [DiskProfile(request, n) for n in profiles] + + +def host_diskprofile_create(request, **kwargs): + host_id = kwargs['host_id'] + del kwargs['host_id'] + + ihost = cgtsclient(request).ihost.get(host_id) + kwargs['ihost_uuid'] = ihost.uuid + + kwargs['profiletype'] = 'stor' + + # create new stor profile + iprofile = cgtsclient(request).iprofile.create(**kwargs) + return DiskProfileFragmentary(request, iprofile) + + +def host_diskprofile_delete(request, iprofile_uuid): + return cgtsclient(request).iprofile.delete(iprofile_uuid) + + +class MemoryProfile(BaseProfile): + """Wrapper for Inventory Memory Profiles""" + + _attrs = ['uuid', 'profilename', 'memory', 'nodes'] + + def __init__(self, request, apiresource): + super(MemoryProfile, self).__init__(request, apiresource) + + self.memory = [Memory(n) for n in self.memory] + self.nodes = [Node(n) for n in self.nodes] + + +class MemoryProfileFragmentary(MemoryProfile): + def _load_data(self, request): + self.memory = cgtsclient(request).iprofile.list_imemorys(self.uuid) + self.nodes = cgtsclient(request).iprofile.list_inodes(self.uuid) + + +def host_memprofile_list(request): + profiles = cgtsclient(request).iprofile.list_memory_profiles() + return [MemoryProfile(request, n) for n in profiles] + + +def host_memprofile_create(request, **kwargs): + host_id = kwargs['host_id'] + del kwargs['host_id'] + + ihost = cgtsclient(request).ihost.get(host_id) + kwargs['ihost_uuid'] = ihost.uuid + + kwargs['profiletype'] = 'memory' + + # create new memory profile + iprofile = cgtsclient(request).iprofile.create(**kwargs) + return MemoryProfileFragmentary(request, iprofile) + + +def host_memprofile_delete(request, iprofile_uuid): + return cgtsclient(request).iprofile.delete(iprofile_uuid) + + +class EventLog(base.APIResourceWrapper): + """Wrapper for Inventory Customer Logs""" + + _attrs = ['uuid', + 'event_log_id', + 'state', + 'entity_type_id', + 'entity_instance_id', + 'timestamp', + 'severity', + 'reason_text', + 'event_log_type', + 'probable_cause', + 'proposed_repair_action', + 'service_affecting', + 'suppression', + 'suppression_status'] + + def __init__(self, apiresource): + super(EventLog, self).__init__(apiresource) + + +def event_log_list(request, search_opts=None): + paginate = False + if search_opts is None: + search_opts = {} + + limit = search_opts.get('limit', None) + marker = search_opts.get('marker', None) + page_size = base.get_request_page_size(request, limit) + + if 'paginate' in search_opts: + paginate = search_opts.pop('paginate') + if paginate: + limit = page_size + 1 + + query = None + alarms = False + logs = False + include_suppress = False + + if "evtType" in search_opts: + evtType = search_opts.pop('evtType') + if evtType == FM_ALARM: + alarms = True + elif evtType == FM_LOG: + logs = True + + if "suppression" in search_opts: + suppression = search_opts.pop('suppression') + + if suppression == FM_SUPPRESS_SHOW: + include_suppress = True + elif suppression == FM_SUPPRESS_HIDE: + include_suppress = False + + logs = cgtsclient(request)\ + .event_log.list(q=query, + limit=limit, + marker=marker, + alarms=alarms, + logs=logs, + include_suppress=include_suppress) + + has_more_data = False + if paginate and len(logs) > page_size: + logs.pop(-1) + has_more_data = True + elif paginate and len(logs) > getattr(settings, 'API_RESULT_LIMIT', 1000): + has_more_data = True + + return [EventLog(n) for n in logs], has_more_data + + +def event_log_get(request, event_log_id): + log = cgtsclient(request).event_log.get(event_log_id) + if not log: + raise ValueError('No match found for event_log_id "%s".' % + event_log_id) + return EventLog(log) + + +class EventSuppression(base.APIResourceWrapper): + """Wrapper for Inventory Alarm Suppression""" + + _attrs = ['uuid', + 'alarm_id', + 'description', + 'suppression_status'] + + def __init__(self, apiresource): + super(EventSuppression, self).__init__(apiresource) + + +def event_suppression_list(request): + + suppression_list = cgtsclient(request).event_suppression.list() + + return [EventSuppression(n) for n in suppression_list] + + +def event_suppression_update(request, event_suppression_uuid, **kwargs): + patch = [] + for key, value in kwargs.iteritems(): + patch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request)\ + .event_suppression.update(event_suppression_uuid, patch) + + +class Device(base.APIResourceWrapper): + """Wrapper for Inventory Devices""" + + _attrs = ['uuid', 'name', 'pciaddr', 'host_uuid', + 'pclass_id', 'pvendor_id', 'pdevice_id', + 'pclass', 'pvendor', 'pdevice', + 'numa_node', 'enabled', 'extra_info', + 'sriov_totalvfs', 'sriov_numvfs', 'sriov_vfs_pci_address'] + + def __init__(self, apiresource): + super(Device, self).__init__(apiresource) + if not self.name: + self.name = '(' + str(self.uuid)[-8:] + ')' + + +def host_device_list(request, host_id): + devices = cgtsclient(request).pci_device.list(host_id) + return [Device(n) for n in devices] + + +def device_list_all(request): + devices = cgtsclient(request).pci_device.list_all() + return [Device(n) for n in devices] + + +def host_device_get(request, device_uuid): + device = cgtsclient(request).pci_device.get(device_uuid) + if device: + return Device(device) + raise ValueError('No match found for device "%s".' % device_uuid) + + +def host_device_update(request, device_uuid, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).pci_device.update(device_uuid, mypatch) + + +class LldpNeighbour(base.APIResourceWrapper): + """Wrapper for Inventory LLDP Neighbour""" + + _attrs = ['port_uuid', + 'port_name', + 'port_namedisplay', + 'uuid', + 'host_uuid', + 'msap', + 'chassis_id', + 'port_identifier', + 'port_description', + 'ttl', + 'system_name', + 'system_description', + 'system_capabilities', + 'management_address', + 'dot1_port_vid', + 'dot1_proto_vids', + 'dot1_vlan_names', + 'dot1_proto_ids', + 'dot1_vid_digest', + 'dot1_management_vid', + 'dot1_lag', + 'dot3_mac_status', + 'dot3_power_mdi', + 'dot3_max_frame'] + + def __init__(self, apiresource): + super(LldpNeighbour, self).__init__(apiresource) + + def get_local_port_display_name(self): + if self.port_name: + return self.port_name + if self.port_namedisplay: + return self.port_namedisplay + else: + return '(' + str(self.port_uuid)[-8:] + ')' + + +def host_lldpneighbour_list(request, host_uuid): + neighbours = cgtsclient(request).lldp_neighbour.list(host_uuid) + return [LldpNeighbour(n) for n in neighbours] + + +def host_lldpneighbour_get(request, neighbour_uuid): + neighbour = cgtsclient(request).lldp_neighbour.get(neighbour_uuid) + + if not neighbour: + raise ValueError('No match found for neighbour id "%s".' % + neighbour_uuid) + return LldpNeighbour(neighbour) + + +def port_lldpneighbour_list(request, port_uuid): + neighbours = cgtsclient(request).lldp_neighbour.list_by_port(port_uuid) + return [LldpNeighbour(n) for n in neighbours] + + +class ServiceParameter(base.APIResourceWrapper): + """Wrapper for Service Parameter configuration""" + + _attrs = ['uuid', 'service', 'section', 'name', 'value'] + + def __init__(self, apiresource): + super(ServiceParameter, self).__init__(apiresource) + + +def service_parameter_list(request): + parameters = cgtsclient(request).service_parameter.list() + return [ServiceParameter(n) for n in parameters] + + +class SDNController(base.APIResourceWrapper): + """Wrapper for SDN Controller configuration""" + + _attrs = ['uuid', 'ip_address', 'port', 'transport', 'state', + 'created_at', 'updated_at'] + + def __init__(self, apiresource): + super(SDNController, self).__init__(apiresource) + + +def sdn_controller_list(request): + controllers = cgtsclient(request).sdn_controller.list() + return [SDNController(n) for n in controllers] + + +def sdn_controller_get(request, uuid): + controller = cgtsclient(request).sdn_controller.get(uuid) + + if not controller: + raise ValueError('No match found for SDN controller id "%s".' % + uuid) + return SDNController(controller) + + +def sdn_controller_create(request, **kwargs): + controller = cgtsclient(request).sdn_controller.create(**kwargs) + return SDNController(controller) + + +def sdn_controller_update(request, uuid, **kwargs): + mypatch = [] + for key, value in kwargs.iteritems(): + mypatch.append(dict(path='/' + key, value=value, op='replace')) + return cgtsclient(request).sdn_controller.update(uuid, mypatch) + + +def sdn_controller_delete(request, uuid): + return cgtsclient(request).sdn_controller.delete(uuid) + + +def get_sdn_enabled(request): + # The SDN enabled flag is present in the Capabilities + # of the system table, however capabilties is not exposed + # as an attribute through system_list() or system_get() + # at this level. We will therefore check the platform.conf + # to see if SDN is configured. + try: + with open(PLATFORM_CONFIGURATION, 'r') as fd: + content = fd.readlines() + sdn_enabled = None + for line in content: + if 'sdn_enabled' in line: + sdn_enabled = line + break + sdn_enabled = sdn_enabled.strip('\n').split('=', 1) + return (sdn_enabled[1].lower() == 'yes') + except Exception: + return False + + +def get_sdn_l3_mode_enabled(request): + # Get the Service Parameter list on this host + # and ensure that the L3 Enabled service parameter + # is set. + try: + allowed_vals = SERVICE_PARAM_ODL_ROUTER_PLUGINS + parameters = service_parameter_list(request) + for parameter in parameters: + if ((parameter.service == SERVICE_TYPE_NETWORK) and + (parameter.section == SERVICE_PARAM_SECTION_NETWORK_DEFAULT) and + (parameter.name == SERVICE_PARAM_NAME_DEFAULT_SERVICE_PLUGINS)): + return(any(sp in allowed_vals + for sp in parameter.value.split(','))) + except Exception: + pass + return False + + +def is_system_mode_simplex(request): + systems = system_list(request) + system_mode = systems[0].to_dict().get('system_mode') + if system_mode == constants.SYSTEM_MODE_SIMPLEX: + return True + return False + + +def get_system_type(request): + systems = system_list(request) + system_type = systems[0].to_dict().get('system_type') + return system_type diff --git a/cgcs_dashboard/api/vim.py b/cgcs_dashboard/api/vim.py new file mode 100755 index 00000000..8e136fa9 --- /dev/null +++ b/cgcs_dashboard/api/vim.py @@ -0,0 +1,129 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging +import urlparse + +from openstack_dashboard.api import base + +from nfv_client.openstack import sw_update + +LOG = logging.getLogger(__name__) + + +STRATEGY_SW_PATCH = 'sw-patch' +STRATEGY_SW_UPGRADE = 'sw-upgrade' + + +class Client(object): + def __init__(self, url, token_id): + self.url = url + self.token_id = token_id + + def get_strategy(self, strategy_name): + return sw_update.get_strategies(self.token_id, self.url, strategy_name) + + def create_strategy( + self, strategy_name, controller_apply_type, storage_apply_type, + swift_apply_type, compute_apply_type, max_parallel_compute_hosts, + default_instance_action, alarm_restrictions): + return sw_update.create_strategy( + self.token_id, self.url, strategy_name, controller_apply_type, + storage_apply_type, + swift_apply_type, compute_apply_type, max_parallel_compute_hosts, + default_instance_action, alarm_restrictions) + + def delete_strategy(self, strategy_name, force): + return sw_update.delete_strategy(self.token_id, self.url, + strategy_name, force) + + def apply_strategy(self, strategy_name, stage_id): + return sw_update.apply_strategy(self.token_id, self.url, strategy_name, + stage_id) + + def abort_strategy(self, strategy_name, stage_id): + return sw_update.abort_strategy(self.token_id, self.url, strategy_name, + stage_id) + + +def _sw_update_client(request): + o = urlparse.urlparse(base.url_for(request, 'nfv')) + url = "://".join((o.scheme, o.netloc)) + return Client(url, token_id=request.user.token.id) + + +def get_strategy(request, strategy_name): + strategy = _sw_update_client(request).get_strategy(strategy_name) + return strategy + + +def create_strategy( + request, strategy_name, controller_apply_type, storage_apply_type, + swift_apply_type, compute_apply_type, max_parallel_compute_hosts, + default_instance_action, alarm_restrictions): + strategy = _sw_update_client(request).create_strategy( + strategy_name, controller_apply_type, storage_apply_type, + swift_apply_type, compute_apply_type, max_parallel_compute_hosts, + default_instance_action, alarm_restrictions) + return strategy + + +def delete_strategy(request, strategy_name, force=False): + response = _sw_update_client(request).delete_strategy(strategy_name, force) + return response + + +def apply_strategy(request, strategy_name, stage_id=None): + response = _sw_update_client(request).apply_strategy(strategy_name, + stage_id) + return response + + +def abort_strategy(request, strategy_name, stage_id=None): + response = _sw_update_client(request).abort_strategy(strategy_name, + stage_id) + return response + + +def get_stages(request, strategy_name): + strategy = _sw_update_client(request).get_strategy(strategy_name) + if not strategy: + return [] + stages = [] + for stage in strategy.build_phase.stages: + phase = strategy.build_phase + phase.stages = None + stage.phase = phase + stages.append(stage) + for stage in strategy.apply_phase.stages: + phase = strategy.apply_phase + phase.stages = None + stage.phase = phase + stages.append(stage) + for stage in strategy.abort_phase.stages: + phase = strategy.abort_phase + phase.stages = None + stage.phase = phase + stages.append(stage) + + return stages + + +def get_stage(request, strategy_name, phase_name, stage_id): + stages = get_stages(request, strategy_name) + for stage in stages: + if stage.phase.phase_name == phase_name and \ + str(stage.stage_id) == str(stage_id): + return stage + + +def get_step(request, strategy_name, phase_name, stage_id, step_id): + stage = get_stage(request, strategy_name, phase_name, stage_id) + for step in stage.steps: + if str(step.step_id) == str(step_id): + return step diff --git a/cgcs_dashboard/dashboards/__init__.py b/cgcs_dashboard/dashboards/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/__init__.py b/cgcs_dashboard/dashboards/admin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/fault_management/__init__.py b/cgcs_dashboard/dashboards/admin/fault_management/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/fault_management/panel.py b/cgcs_dashboard/dashboards/admin/fault_management/panel.py new file mode 100755 index 00000000..da127cee --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/panel.py @@ -0,0 +1,50 @@ +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. + +from django.utils.translation import ugettext_lazy as _ # noqa + +import horizon +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class FaultManagement(horizon.Panel): + name = _("Fault Management") + slug = 'fault_management' + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(FaultManagement, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + + +dashboard.Admin.register(FaultManagement) diff --git a/cgcs_dashboard/dashboards/admin/fault_management/tables.py b/cgcs_dashboard/dashboards/admin/fault_management/tables.py new file mode 100755 index 00000000..17c62866 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/tables.py @@ -0,0 +1,284 @@ +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. + + +from django.utils.html import escape as escape_html +from django.utils.safestring import mark_safe + +from django.utils.translation import pgettext_lazy +from django.utils.translation import ugettext_lazy as _ # noqa +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables +from horizon.utils import filters as utils_filters +from openstack_dashboard import api +from openstack_dashboard.api import sysinv + +SUPPRESSION_STATUS_CHOICES = ( + ("suppressed", False), + ("unsuppressed", True), + ("None", True), +) +SUPPRESSION_STATUS_DISPLAY_CHOICES = ( + ("suppressed", pgettext_lazy("Indicates this type of alarm \ + is suppressed", u"suppressed")), + ("unsuppressed", pgettext_lazy("Indicates this type of alarm \ + is unsuppressed", u"unsuppressed")), + ("None", pgettext_lazy("Indicates an event type", u"None")), +) + + +class AlarmsLimitAction(tables.LimitAction): + verbose_name = _("Alarms") + + +class AlarmFilterAction(tables.FixedWithQueryFilter): + def __init__(self, **kwargs): + super(AlarmFilterAction, self).__init__(**kwargs) + + self.filter_choices = [ + ( + (sysinv.FM_SUPPRESS_SHOW, _("Show Suppressed"), True), + (sysinv.FM_SUPPRESS_HIDE, _('Hide Suppressed'), True) + ) + ] + self.default_value = sysinv.FM_SUPPRESS_HIDE + + self.disabled_choices = ['enabled'] + + +class AlarmsTable(tables.DataTable): + alarm_id = tables.Column('alarm_id', + link="horizon:admin:fault_management:detail", + verbose_name=_('Alarm ID')) + reason_text = tables.Column('reason_text', + verbose_name=_('Reason Text')) + entity_instance_id = tables.Column('entity_instance_id', + verbose_name=_('Entity Instance ID')) + suppression_status = \ + tables.Column('suppression_status', + verbose_name=_('Suppression Status'), + status=True, + status_choices=SUPPRESSION_STATUS_CHOICES, + display_choices=SUPPRESSION_STATUS_DISPLAY_CHOICES) + severity = tables.Column('severity', + verbose_name=_('Severity')) + timestamp = tables.Column('timestamp', + attrs={'data-type': 'timestamp'}, + filters=(utils_filters.parse_isotime,), + verbose_name=_('Timestamp')) + + def get_object_id(self, obj): + return obj.uuid + + class Meta(object): + name = "alarms" + verbose_name = _("Active Alarms") + status_columns = ["suppression_status"] + limit_param = "alarm_limit" + pagination_param = "alarm_marker" + prev_pagination_param = 'prev_alarm_marker' + table_actions = (AlarmFilterAction, AlarmsLimitAction) + multi_select = False + hidden_title = False + + +class EventLogsLimitAction(tables.LimitAction): + verbose_name = _("Events") + + +class EventLogsFilterAction(tables.FixedWithQueryFilter): + def __init__(self, **kwargs): + super(EventLogsFilterAction, self).__init__(**kwargs) + + self.filter_choices = [ + ( + (sysinv.FM_ALL, _("All Events"), True), + (sysinv.FM_ALARM, _('Alarm Events'), True), + (sysinv.FM_LOG, _('Log Events'), True), + ), + ( + (sysinv.FM_SUPPRESS_SHOW, _("Show Suppressed"), True), + (sysinv.FM_SUPPRESS_HIDE, _('Hide Suppressed'), True) + ) + ] + self.default_value = sysinv.FM_ALL_SUPPRESS_HIDE + + self.disabled_choices = ['enabled', 'enabled'] + + +class EventLogsTable(tables.DataTable): + timestamp = tables.Column('timestamp', + attrs={'data-type': 'timestamp'}, + filters=(utils_filters.parse_isotime,), + verbose_name=_('Timestamp')) + + state = tables.Column('state', verbose_name=_('State')) + event_log_id = tables.Column('event_log_id', + link="horizon:admin:fault_management:" + "eventlogdetail", + verbose_name=_('ID')) + reason_text = tables.Column('reason_text', verbose_name=_('Reason Text')) + entity_instance_id = tables.Column('entity_instance_id', + verbose_name=_('Entity Instance ID')) + suppression_status = \ + tables.Column('suppression_status', + verbose_name=_('Suppression Status'), + status=True, + status_choices=SUPPRESSION_STATUS_CHOICES, + display_choices=SUPPRESSION_STATUS_DISPLAY_CHOICES) + severity = tables.Column('severity', verbose_name=_('Severity')) + + def get_object_id(self, obj): + return obj.uuid + + class Meta(object): + name = "eventLogs" + verbose_name = _("Events") + status_columns = ["suppression_status"] + table_actions = (EventLogsFilterAction, + EventLogsLimitAction,) + limit_param = "event_limit" + pagination_param = "event_marker" + prev_pagination_param = 'prev_event_marker' + multi_select = False + + +class SuppressEvent(tables.BatchAction): + name = "suppress" + action_type = 'danger' + icon = "remove" + help_text = _("Events with selected Alarm ID will be suppressed.") + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Suppress Event", + u"Suppress Event", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Events suppressed for Alarm ID", + u"Events suppressed for Alarm ID", + count + ) + + def allowed(self, request, datum): + """Allow suppress action if Alarm ID is unsuppressed.""" + if datum.suppression_status == sysinv.FM_SUPPRESSED: + return False + + return True + + def action(self, request, obj_id): + kwargs = {"suppression_status": sysinv.FM_SUPPRESSED} + + try: + api.sysinv.event_suppression_update(request, obj_id, **kwargs) + except Exception: + exceptions.handle(request, + _('Unable to set specified alarm type to \ + suppressed\'s.')) + + +class UnsuppressEvent(tables.BatchAction): + name = "unsuppress" + action_type = 'danger' + icon = "remove" + help_text = _("Events with selected Alarm ID will be unsuppressed.") + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Unsuppress Event", + u"Unsuppress Event", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Events unsuppressed for Alarm ID", + u"Events unsuppressed for Alarm ID", + count + ) + + def allowed(self, request, datum): + """Allow unsuppress action if Alarm ID is suppressed.""" + if datum.suppression_status == sysinv.FM_UNSUPPRESSED: + return False + + return True + + def action(self, request, obj_id): + kwargs = {"suppression_status": sysinv.FM_UNSUPPRESSED} + + try: + api.sysinv.event_suppression_update(request, obj_id, **kwargs) + except Exception: + exceptions.handle(request, + _('Unable to set specified alarm type to \ + unsuppressed\'s.')) + + +class EventsSuppressionTable(tables.DataTable): + # noinspection PyMethodParameters + def description_inject(row_data): + description = \ + escape_html(str(row_data.description)).replace("\n", "
") + description = description.replace("\t", "    ") + description = description.replace(" " * 4, " " * 4) + description = description.replace(" " * 3, " " * 3) + description = description.replace(" " * 2, " " * 2) + return mark_safe(description) + + alarm_id = tables.Column('alarm_id', + verbose_name=_('Event ID')) + description = tables.Column(description_inject, + verbose_name=_('Description')) + status = tables.Column('suppression_status', + verbose_name=_('Status')) + + def get_object_id(self, obj): + # return obj.alarm_id + return obj.uuid + + def get_object_display(self, datum): + """Returns a display name that identifies this object.""" + if hasattr(datum, 'alarm_id'): + return datum.alarm_id + return None + + class Meta(object): + name = "eventsSuppression" + limit_param = "events_suppression_limit" + pagination_param = "events_suppression_marker" + prev_pagination_param = 'prev_event_ids_marker' + verbose_name = _("Events Suppression") + row_actions = (SuppressEvent, UnsuppressEvent,) + multi_select = False diff --git a/cgcs_dashboard/dashboards/admin/fault_management/tabs.py b/cgcs_dashboard/dashboards/admin/fault_management/tabs.py new file mode 100755 index 00000000..56d652de --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/tabs.py @@ -0,0 +1,318 @@ +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. + + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import tabs +from openstack_dashboard import api +from openstack_dashboard.api import sysinv +from openstack_dashboard.dashboards.admin.fault_management import tables + +ALARMS_SUPPRESSION_FILTER_GROUP = 0 +EVENT_SUPPRESSION_FILTER_GROUP = 1 + + +class ActiveAlarmsTab(tabs.TableTab): + table_classes = (tables.AlarmsTable,) + name = _("Active Alarms") + slug = "alarms" + template_name = 'admin/fault_management/_active_alarms.html' + + def has_more_data(self, table): + return self._more + + def get_limit_count(self, table): + return self._limit + + def getTableFromName(self, tableName): + table = self._tables[tableName] + return table + + def set_suppression_filter(self, disabled_status): + alarmsTable = self.getTableFromName('alarms') + filter_action = alarmsTable._meta._filter_action + filter_action.set_disabled_filter_field_for_group( + ALARMS_SUPPRESSION_FILTER_GROUP, disabled_status) + filter_action.updateFromRequestDataToSession(self.request) + + def get_context_data(self, request): + context = super(ActiveAlarmsTab, self).get_context_data(request) + + summary = api.sysinv.alarm_summary_get( + self.request, include_suppress=False) + context["total"] = summary.critical + summary.major + summary.minor \ + + summary.warnings + context["summary"] = summary + + events_types = self.get_event_suppression_data() + suppressed_events_types = len([etype for etype + in events_types + if etype.suppression_status == + 'suppressed']) + + alarms_table = self.getTableFromName('alarms') + + suppress_filter = self.get_filters() + suppress_filter_state = suppress_filter.get('suppression') + + hidden_found = 'hidden' in alarms_table.columns["suppression_status"].\ + classes + + if not hidden_found: + if suppressed_events_types == 0: + self.set_suppression_filter('disabled') + alarms_table.columns["suppression_status"]\ + .classes.append('hidden') + elif suppress_filter_state == sysinv.FM_SUPPRESS_HIDE: + self.set_suppression_filter('enabled') + alarms_table.columns["suppression_status"].classes\ + .append('hidden') + else: + if suppressed_events_types == 0: + self.set_suppression_filter('disabled') + else: + self.set_suppression_filter('enabled') + if suppress_filter_state == sysinv.FM_SUPPRESS_SHOW: + alarms_table.columns["suppression_status"].classes\ + .remove('hidden') + + return context + + def get_filters(self, filters=None): + + filters = filters or {} + alarmsTable = self.getTableFromName('alarms') + filter_action = alarmsTable._meta._filter_action + filter_action.updateFromRequestDataToSession(self.request) + filter_field = filter_action.get_filter_field(self.request) + + if filter_field: + suppression = filter_action.get_filter_field_for_group(0) + filters["suppression"] = suppression + + return filters + + def get_alarms_data(self): + search_opts = {} + + # get retrieve parameters from request/session env + marker = \ + self.request.GET.get(tables.AlarmsTable._meta.pagination_param, + None) + limit = \ + self.request.GET.get(tables.AlarmsTable._meta.limit_param, + None) + + search_opts = self.get_filters() + search_opts.update({'marker': marker, + 'limit': limit, + 'paginate': True, + 'sort_key': 'severity,entity_instance_id', + 'sort_dir': 'asc'}) + + alarms = [] + try: + if 'paginate' in search_opts: + alarms, self._more = api.sysinv.alarm_list( + self.request, search_opts=search_opts) + else: + alarms = api.sysinv.alarm_list( + self.request, search_opts=search_opts) + self._limit = limit + except Exception: + self._more = False + self._limit = None + exceptions.handle(self.request, + _('Unable to retrieve alarms list.')) + return alarms + + def get_event_suppression_data(self): + event_types = [] + + try: + if 'suppression_list' not in self.tab_group.kwargs: + self.tab_group.kwargs['suppression_list'] = \ + api.sysinv.event_suppression_list(self.request) + event_types = self.tab_group.kwargs['suppression_list'] + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve event suppression table \ ' + 'list.')) + return event_types + + +class EventLogTab(tabs.TableTab): + table_classes = (tables.EventLogsTable,) + name = _("Events") + slug = "eventLogs" + template_name = 'admin/fault_management/_summary.html' + preload = False + + def has_more_data(self, table): + return self._more + + def get_limit_count(self, table): + return self._limit + + def getTableFromName(self, tableName): + table = self._tables[tableName] + return table + + def set_suppression_filter(self, disabled_status): + alarmsTable = self.getTableFromName('eventLogs') + filter_action = alarmsTable._meta._filter_action + filter_action.set_disabled_filter_field_for_group( + EVENT_SUPPRESSION_FILTER_GROUP, disabled_status) + filter_action.updateFromRequestDataToSession(self.request) + + def get_context_data(self, request): + context = super(EventLogTab, self).get_context_data(request) + + events_types = self.get_event_suppression_data() + suppressed_events_types = len([etype for etype in events_types + if etype.suppression_status == + 'suppressed']) + + event_log_table = self.getTableFromName('eventLogs') + + filters = self.get_filters({'marker': None, + 'limit': None, + 'paginate': True}) + + suppress_filter_state = filters.get('suppression') + + hidden_found = 'hidden' in event_log_table\ + .columns["suppression_status"].classes + + if not hidden_found: + if suppressed_events_types == 0: + self.set_suppression_filter('disabled') + event_log_table.columns["suppression_status"]\ + .classes.append('hidden') + elif suppress_filter_state == sysinv.FM_SUPPRESS_HIDE: + self.set_suppression_filter('enabled') + event_log_table.columns["suppression_status"].\ + classes.append('hidden') + else: + if suppressed_events_types == 0: + self.set_suppression_filter('disabled') + else: + self.set_suppression_filter('enabled') + if suppress_filter_state == sysinv.FM_SUPPRESS_SHOW: + event_log_table.columns["suppression_status"]\ + .classes.remove('hidden') + + return context + + def get_filters(self, filters): + + eventLogsTable = self.getTableFromName('eventLogs') + filter_action = eventLogsTable._meta._filter_action + filter_action.updateFromRequestDataToSession(self.request) + filter_field = filter_action.get_filter_field(self.request) + + if filter_field: + filters["evtType"] = filter_action.get_filter_field_for_group(0) + filters["suppression"] = filter_action\ + .get_filter_field_for_group(1) + + return filters + + def get_eventLogs_data(self): + + # get retrieve parameters from request/session env + marker = \ + self.request.GET.get(tables.EventLogsTable._meta.pagination_param, + None) + limit = \ + self.request.GET.get(tables.EventLogsTable._meta.limit_param, + None) + search_opts = self.get_filters({'marker': marker, + 'limit': limit, + 'paginate': True}) + events = [] + + try: + # now retrieve data from rest API + events, self._more = \ + api.sysinv.event_log_list(self.request, + search_opts=search_opts) + self._limit = limit + return events + + except Exception: + events = [] + self._more = False + self._limit = None + exceptions.handle(self.request, + _('Unable to retrieve Event Log list.')) + + return events + + def get_event_suppression_data(self): + event_types = [] + + try: + if 'suppression_list' not in self.tab_group.kwargs: + self.tab_group.kwargs['suppression_list'] = \ + api.sysinv.event_suppression_list(self.request) + event_types = self.tab_group.kwargs['suppression_list'] + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve event suppression \ + table list.')) + return event_types + + +class EventsSuppressionTab(tabs.TableTab): + table_classes = (tables.EventsSuppressionTable,) + name = _("Events Suppression") + slug = "eventsSuppression" + template_name = 'admin/fault_management/_summary.html' + preload = False + + def get_eventsSuppression_data(self): + event_suppression_list = [] + + try: + if 'suppression_list' not in self.tab_group.kwargs: + self.tab_group.kwargs['suppression_list'] = \ + api.sysinv.event_suppression_list(self.request) + event_suppression_list = self.tab_group.kwargs['suppression_list'] + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve event suppression \ + list\'s.')) + + event_suppression_list.sort(key=lambda a: (a.alarm_id)) + + return event_suppression_list + + +class AlarmsTabs(tabs.TabGroup): + slug = "alarms_tabs" + tabs = (ActiveAlarmsTab, EventLogTab, EventsSuppressionTab) + sticky = True diff --git a/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_active_alarms.html b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_active_alarms.html new file mode 100755 index 00000000..56851328 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_active_alarms.html @@ -0,0 +1,26 @@ +{% load i18n %} + +
+ + {% trans "Active Alarms" %}: + {{ total }} + + + {% trans "Critical" %}: + {{ summary.critical }} + + + {% trans "Major" %}: + {{ summary.major }} + + + {% trans "Minor" %}: + {{ summary.minor }} + + + {% trans "Warning" %}: + {{ summary.warnings }} + +
+ +{{ table.render }} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_history.html b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_history.html new file mode 100755 index 00000000..2a328fd6 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_history.html @@ -0,0 +1,58 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Historical Alarm Details" %}{% endblock %} + +{% block main %} +{% if history.event_log_id == '' or history.event_log_id == ' ' %} +

{{history.reason_text }}

+{% else %} +

{{history.state }} - {{history.event_log_id }} - {{history.reason_text }}

+{% endif %} + +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "Alarm UUID" %}
+
{{ history.uuid }}
+ {% if history.event_log_id != '' and history.event_log_id != ' ' %} +
{% trans "Alarm ID" %}
+
{{ history.event_log_id }}
+ {% endif %} +
{% trans "Severity" %}
+
{{ history.severity }}
+
{% trans "Alarm State" %}
+
{{ history.state }}
+
{% trans "Alarm Type" %}
+
{{ history.event_log_type }}
+
{% trans "Timestamp" %}
+
{{ history.timestamp|parse_isotime }}
+
{% trans "Suppression" %}
+
{{ history.suppression }}
+
+
+
{% trans "Entity Instance ID" %}
+
{{ history.entity_instance_id }}
+ {% if history.entity_type_id != '' and history.entity_type_id != ' ' %} +
{% trans "Entity Type ID" %}
+
{{ history.entity_type_id }}
+ {% endif %} +
{% trans "Probable Cause" %}
+
{{ history.probable_cause }}
+ {% if history.proposed_repair_action != '' and history.proposed_repair_action != ' ' %} +
{% trans "Proposed Repair Action" %}
+
{{ history.proposed_repair_action }}
+ {% endif %} +
{% trans "Service Affecting" %}
+
{{ history.service_affecting }}
+ {% if history.reason_text != '' and history.reason_text != ' ' %} +
{% trans "Reason" %}
+
{{ history.reason_text }}
+ {% endif %} +
+
+
+
+{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_log.html b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_log.html new file mode 100755 index 00000000..4ea38e19 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_log.html @@ -0,0 +1,50 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Customer Log Details" %}{% endblock %} + +{% block main %} +{% if log.event_log_id == '' or log.event_log_id == ' ' %} +

{{log.reason_text }}

+{% else %} +

{{log.event_log_id }} - {{log.reason_text }}

+{% endif %} + +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "Log UUID" %}
+
{{ log.uuid }}
+ {% if log.event_log_id != '' and log.event_log_id != ' ' %} +
{% trans "Log ID" %}
+
{{ log.event_log_id }}
+ {% endif %} +
{% trans "Severity" %}
+
{{ log.severity }}
+
{% trans "Log Type" %}
+
{{ log.event_log_type }}
+
{% trans "Timestamp" %}
+
{{ log.timestamp|parse_isotime }}
+
+
+
{% trans "Entity Instance ID" %}
+
{{ log.entity_instance_id }}
+ {% if log.entity_type_id != '' and log.entity_type_id != ' ' %} +
{% trans "Entity Type ID" %}
+
{{ log.entity_type_id }}
+ {% endif %} +
{% trans "Probable Cause" %}
+
{{ log.probable_cause }}
+
{% trans "Service Affecting" %}
+
{{ log.service_affecting }}
+ {% if log.reason_text != '' and log.reason_text != ' ' %} +
{% trans "Reason" %}
+
{{ log.reason_text }}
+ {% endif %} +
+
+
+
+{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_overview.html b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_overview.html new file mode 100755 index 00000000..f004385a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_detail_overview.html @@ -0,0 +1,52 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Alarm Details" %}{% endblock %} + +{% block main %} +

{{alarm.alarm_id }} - {{alarm.reason_text }}

+ +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "Alarm UUID" %}
+
{{ alarm.uuid }}
+
{% trans "Alarm ID" %}
+
{{ alarm.alarm_id }}
+
{% trans "Severity" %}
+
{{ alarm.severity }}
+
{% trans "Alarm State" %}
+
{{ alarm.alarm_state }}
+
{% trans "Alarm Type" %}
+
{{ alarm.alarm_type }}
+
{% trans "Timestamp" %}
+
{{ alarm.timestamp|parse_isotime }}
+
{% trans "Suppression" %}
+
{{ alarm.suppression }}
+
+
+
{% trans "Entity Instance ID" %}
+
{{ alarm.entity_instance_id }}
+
{% trans "Entity Type ID" %}
+
{{ alarm.entity_type_id }}
+
{% trans "Probable Cause" %}
+
{{ alarm.probable_cause }}
+
{% trans "Proposed Repair Action" %}
+
{{ alarm.proposed_repair_action }}
+
{% trans "Service Affecting" %}
+
{{ alarm.service_affecting }}
+
{% trans "Management Affecting" %}
+
{{ alarm.mgmt_affecting }}
+ {% if alarm.reason_text != '' and alarm.reason_text != ' ' %} +
{% trans "Reason" %}
+
{{ alarm.reason_text }}
+ {% endif %} +
+
+
+
+{% endblock %} + + diff --git a/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_summary.html b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_summary.html new file mode 100755 index 00000000..d187f111 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/_summary.html @@ -0,0 +1,5 @@ +{% load i18n %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/index.html b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/index.html new file mode 100755 index 00000000..ee081fae --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/templates/fault_management/index.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Fault Management" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Fault Management")%} +{% endblock page_header %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} + + +{% block js %} + {{ block.super }} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/fault_management/urls.py b/cgcs_dashboard/dashboards/admin/fault_management/urls.py new file mode 100755 index 00000000..d3d9f98e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/urls.py @@ -0,0 +1,38 @@ +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. + + +from django.conf.urls import url # noqa + +from openstack_dashboard.dashboards.admin.fault_management import views + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/detail/$', + views.DetailView.as_view(), name='detail'), + url(r'^(?P[^/]+)/eventlogdetail/$', + views.EventLogDetailView.as_view(), name='eventlogdetail'), + url(r'^banner/$', views.BannerView.as_view(), + name='banner') +] diff --git a/cgcs_dashboard/dashboards/admin/fault_management/views.py b/cgcs_dashboard/dashboards/admin/fault_management/views.py new file mode 100755 index 00000000..d4219694 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/fault_management/views.py @@ -0,0 +1,175 @@ +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. + + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ # noqa +from django.views.generic import TemplateView + +from horizon import exceptions +from horizon import tabs +from horizon import views +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.fault_management import \ + tabs as project_tabs + +LOG = logging.getLogger(__name__) + + +class IndexView(tabs.TabbedTableView): + tab_group_class = project_tabs.AlarmsTabs + template_name = 'admin/fault_management/index.html' + page_title = _("Fault Management") + + +class DetailView(views.HorizonTemplateView): + template_name = 'admin/fault_management/_detail_overview.html' + page_title = 'Alarm Details' + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + context["alarm"] = self.get_data() + return context + + def get_data(self): + if not hasattr(self, "_alarm"): + alarm_uuid = self.kwargs['id'] + try: + alarm = api.sysinv.alarm_get(self.request, alarm_uuid) + + except Exception: + redirect = reverse('horizon:admin:fault_management:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'alarm "%s".') % alarm_uuid, + redirect=redirect) + + self._alarm = alarm + return self._alarm + + +class EventLogDetailView(views.HorizonTemplateView): + # Strategy behind this event log detail view is to + # first retrieve the event_log data, then examine the event's + # state property, and from that, determine if it should use + # the _detail_history.html (alarmhistory) template or + # or use the _detail_log.html (customer log) template + + def get_template_names(self): + if self.type == "alarmhistory": + template_name = 'admin/fault_management/_detail_history.html' + else: + template_name = 'admin/fault_management/_detail_log.html' + return template_name + + def _detectEventLogType(self): + if hasattr(self, "type"): + return self.type + if not self._eventlog: + raise Exception("Cannot determine Event Log type for " + "EventLogDetailView. First retrieve " + "Eventlog data") + if self._eventlog.state == "log": + self.type = "log" + elif self._eventlog.state in ["set", "clear"]: + self.type = "alarmhistory" + else: + raise Exception("Invalid state = '{}'. Cannot " + "determine Event log type for " + "event log".format(self._eventlog.state)) + return self.type + + def get_context_data(self, **kwargs): + context = super(EventLogDetailView, self).get_context_data(**kwargs) + data = self.get_data() + if self.type == "alarmhistory": + self.page_title = 'Historical Alarm Details' + self.template_name = 'admin/fault_management/_detail_history.html' + context["history"] = data + else: + self.page_title = 'Customer Log Detail' + self.template_name = 'admin/fault_management/_detail_log.html' + context["log"] = data + + return context + + def get_data(self): + if not hasattr(self, "_eventlog"): + uuid = self.kwargs['id'] + try: + self._eventlog = api.sysinv.event_log_get(self.request, uuid) + self._detectEventLogType() + except Exception: + redirect = reverse('horizon:admin:fault_management:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'event log "%s".') % uuid, + redirect=redirect) + return self._eventlog + + +class BannerView(TemplateView): + template_name = 'header/_alarm_banner.html' + + def get_context_data(self, **kwargs): + context = super(TemplateView, self).get_context_data(**kwargs) + + if not self.request.is_ajax(): + raise exceptions.NotFound() + + if (not self.request.user.is_authenticated() or + not self.request.user.is_superuser): + context["alarmbanner"] = False + elif 'dc_admin' in self.request.META.get('HTTP_REFERER'): + summaries = self.get_subcloud_data() + central_summary = self.get_data() + summaries.append(central_summary) + context["dc_admin"] = True + context["alarmbanner"] = True + context["OK"] = len( + [s for s in summaries if s.status == 'OK']) + context["degraded"] = len( + [s for s in summaries if s.status == 'degraded']) + context["critical"] = len( + [s for s in summaries if s.status == 'critical']) + context["disabled"] = len( + [s for s in summaries if s.status == 'disabled']) + elif api.base.is_TiS_region(self.request): + context["summary"] = self.get_data() + context["alarmbanner"] = True + return context + + def get_data(self): + summary = None + try: + summary = api.sysinv.alarm_summary_get(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve alarm summary.')) + return summary + + def get_subcloud_data(self): + return api.dc_manager.alarm_summary_list(self.request) diff --git a/cgcs_dashboard/dashboards/admin/host_topology/__init__.py b/cgcs_dashboard/dashboards/admin/host_topology/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/host_topology/panel.py b/cgcs_dashboard/dashboards/admin/host_topology/panel.py new file mode 100755 index 00000000..abfb43a7 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/panel.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class HostTopology(horizon.Panel): + name = _("Provider Network Topology") + slug = 'host_topology' + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(HostTopology, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + +dashboard.Admin.register(HostTopology) diff --git a/cgcs_dashboard/dashboards/admin/host_topology/tables.py b/cgcs_dashboard/dashboards/admin/host_topology/tables.py new file mode 100755 index 00000000..a8523008 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/tables.py @@ -0,0 +1,46 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.utils.translation import ugettext_lazy as _ + +from openstack_dashboard.dashboards.admin.fault_management import \ + tables as fm_tables +from openstack_dashboard.dashboards.admin.inventory.interfaces import \ + tables as if_tables +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges import \ + tables as sr_tables + +LOG = logging.getLogger(__name__) + + +class ProviderNetworkRangeTable(sr_tables.ProviderNetworkRangeTable): + class Meta(object): + name = "provider_network_ranges" + verbose_name = _("Segmentation Ranges") + table_actions = () + row_actions = () + + +class AlarmsTable(fm_tables.AlarmsTable): + class Meta(object): + name = "alarms" + verbose_name = _("Related Alarms") + multi_select = False + table_actions = [] + row_actions = [] + + +class InterfacesTable(if_tables.InterfacesTable): + class Meta(object): + name = "interfaces" + verbose_name = _("Interfaces") + multi_select = False + table_actions = [] + row_actions = [] diff --git a/cgcs_dashboard/dashboards/admin/host_topology/tabs.py b/cgcs_dashboard/dashboards/admin/host_topology/tabs.py new file mode 100755 index 00000000..b7438e0a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/tabs.py @@ -0,0 +1,128 @@ +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from collections import OrderedDict +import logging + +from django.core.urlresolvers import reverse_lazy # noqa +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import tabs +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.host_topology import \ + tables as tables +from openstack_dashboard.dashboards.admin.inventory import \ + tabs as i_tabs +from openstack_dashboard.dashboards.admin.providernets.providernets import \ + tables as pn_tables + +LOG = logging.getLogger(__name__) + + +def get_alarms_for_entity(alarms, entity_str): + matched = [] + for alarm in alarms: + for id in alarm.entity_instance_id.split('.'): + try: + if entity_str == id.split('=')[1]: + matched.append(alarm) + except Exception: + # malformed entity_instance_id + pass + return matched + + +class AlarmsTab(tabs.TableTab): + table_classes = (tables.AlarmsTable,) + name = _("Related Alarms") + slug = "alarm_tab" + template_name = ("admin/host_topology/detail/_detail_alarms.html") + + def get_alarms_data(self): + entity = self.tab_group.kwargs.get('host') + if not entity: + entity = self.tab_group.kwargs.get('providernet') + return entity.alarms + + +class InterfacesTab(i_tabs.InterfacesTab): + table_classes = (tables.InterfacesTable, ) + + +class HostDetailTabs(tabs.TabGroup): + slug = "host_details" + tabs = (i_tabs.OverviewTab, AlarmsTab, InterfacesTab, + i_tabs.LldpTab) + sticky = True + + +class OverviewTab(tabs.TableTab): + table_classes = (tables.ProviderNetworkRangeTable, + pn_tables.ProviderNetworkTenantNetworkTable) + template_name = 'admin/host_topology/detail/providernet.html' + name = "Provider Network Detail" + slug = 'providernet_details_overview' + failure_url = reverse_lazy('horizon:admin:host_topology:index') + + def _get_tenant_list(self): + if not hasattr(self, "_tenants"): + try: + tenants, has_more = api.keystone.tenant_list(self.request) + except Exception: + tenants = [] + msg = _('Unable to retrieve instance project information.') + exceptions.handle(self.request, msg) + tenant_dict = OrderedDict([(t.id, t) for t in tenants]) + self._tenants = tenant_dict + return self._tenants + + def get_tenant_networks_data(self): + try: + providernet_id = self.tab_group.kwargs['providernet_id'] + networks = api.neutron.provider_network_list_tenant_networks( + self.request, providernet_id=providernet_id) + except Exception: + networks = [] + msg = _('Tenant network list can not be retrieved.') + exceptions.handle(self.request, msg) + return networks + + def get_provider_network_ranges_data(self): + try: + providernet_id = self.tab_group.kwargs['providernet_id'] + ranges = api.neutron.provider_network_range_list( + self.request, providernet_id=providernet_id) + except Exception: + ranges = [] + msg = _('Segmentation id range list can not be retrieved.') + exceptions.handle(self.request, msg) + tenant_dict = self._get_tenant_list() + for r in ranges: + r.set_id_as_name_if_empty() + # Set tenant name + tenant = tenant_dict.get(r.tenant_id, None) + r.tenant_name = getattr(tenant, 'name', None) + return ranges + + def get_context_data(self, request): + context = super(OverviewTab, self).get_context_data(request) + try: + context['providernet'] = self.tab_group.kwargs['providernet'] + context['nova_providernet'] = \ + self.tab_group.kwargs['nova_providernet'] + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve providernet details.')) + return context + + +class ProvidernetDetailTabs(tabs.TabGroup): + slug = "pnet_details" + tabs = (OverviewTab, AlarmsTab) + sticky = True diff --git a/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/_svg_element.html b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/_svg_element.html new file mode 100755 index 00000000..4fa41ab2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/_svg_element.html @@ -0,0 +1,261 @@ +{% load i18n %} + + +
+
+

Compute Hosts

+ +
+
+

Provider Networks

+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + instance + + + + + + + + + + + + + + + + + + + Network + + + + + + + + + + + + + + + + + + + +
+
diff --git a/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/_detail_alarms.html b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/_detail_alarms.html new file mode 100755 index 00000000..8c94c852 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/_detail_alarms.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ alarms_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/providernet.html b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/providernet.html new file mode 100755 index 00000000..a432f190 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/providernet.html @@ -0,0 +1,13 @@ +{% load i18n sizeformat %} + +
+ {% include "admin/providernets/providernets/_detail_overview.html" %} +
+
+ {{ provider_network_ranges_table.render }} +
+
+ {{ tenant_networks_table.render }} +
+
+ diff --git a/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/tabbed_detail.html b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/tabbed_detail.html new file mode 100755 index 00000000..aa055465 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/detail/tabbed_detail.html @@ -0,0 +1,16 @@ +{% load i18n sizeformat %} + + +{% if host %} +

Selected Entity: {{host.hostname}}

+{% else %} +

Selected Entity: {{providernet.name}}

+{% endif %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/index.html b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/index.html new file mode 100755 index 00000000..2536c4be --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/templates/host_topology/index.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Provider Network Topology" %}{% endblock %} + +{% block main %} + + + +
+
+ +
+
+ +
+
+
{% blocktrans %}There are no hosts or provider networks to display.{% endblocktrans %}
+ {% include "admin/host_topology/_svg_element.html" %} +
{% blocktrans %}Select a host or providernet to view its details. The current view can be moved by clicking and dragging.{% endblocktrans %}
+
+ +
+
+ + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/host_topology/urls.py b/cgcs_dashboard/dashboards/admin/host_topology/urls.py new file mode 100755 index 00000000..abd05a54 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/urls.py @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import url + +from openstack_dashboard.dashboards.admin.host_topology import views + + +urlpatterns = [ + url(r'^$', views.HostTopologyView.as_view(), name='index'), + url(r'^json$', views.JSONView.as_view(), name='json'), + + url(r'^(?P[^/]+)/host/$', + views.HostDetailView.as_view(), name='host'), + url(r'^(?P[^/]+)/providernet/$', + views.ProvidernetDetailView.as_view(), name='providernet') +] diff --git a/cgcs_dashboard/dashboards/admin/host_topology/views.py b/cgcs_dashboard/dashboards/admin/host_topology/views.py new file mode 100755 index 00000000..e5959cd4 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/host_topology/views.py @@ -0,0 +1,235 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import json +import logging + +from django.conf import settings +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.http import HttpResponse # noqa +from django.utils.translation import ugettext_lazy as _ +from django.views.generic import View # noqa + +from horizon import exceptions +from horizon import tabs +from horizon.utils import settings as utils_settings +from horizon import views + +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.host_topology import\ + tabs as topology_tabs +from openstack_dashboard.dashboards.admin.inventory import\ + views as i_views +from openstack_dashboard.usage import quotas + +LOG = logging.getLogger(__name__) + + +class HostDetailView(i_views.DetailView): + tab_group_class = topology_tabs.HostDetailTabs + template_name = 'admin/host_topology/detail/tabbed_detail.html' + + def get_data(self): + if not hasattr(self, "_host"): + try: + host = super(HostDetailView, self).get_data() + + alarms = [] + try: + alarms = api.sysinv.alarm_list(self.request) + except Exception as ex: + exceptions.handle(ex) + # Filter out unrelated alarms + host.alarms = topology_tabs.get_alarms_for_entity( + alarms, host.hostname) + # Sort alarms by severity + host.alarms.sort(key=lambda a: (a.severity)) + + except Exception as ex: + LOG.exception(ex) + raise + self._host = host + return self._host + + +class ProvidernetDetailView(tabs.TabbedTableView): + tab_group_class = topology_tabs.ProvidernetDetailTabs + template_name = 'admin/host_topology/detail/tabbed_detail.html' + failure_url = reverse_lazy('horizon:admin:host_topology:index') + + def get_context_data(self, **kwargs): + context = super(ProvidernetDetailView, self).get_context_data(**kwargs) + context["providernet"] = self.get_data() + context["nova_providernet"] = self.get_nova_data() + return context + + def get_data(self): + if not hasattr(self, "_providernet"): + try: + providernet_id = self.kwargs['providernet_id'] + providernet = api.neutron.provider_network_get( + self.request, providernet_id) + providernet.set_id_as_name_if_empty(length=0) + + alarms = api.sysinv.alarm_list(self.request) + # Filter out unrelated alarms + providernet.alarms = \ + topology_tabs.get_alarms_for_entity(alarms, + providernet.id) + \ + topology_tabs.get_alarms_for_entity(alarms, + providernet.name) + # Sort alarms by severity + providernet.alarms.sort(key=lambda a: (a.severity)) + + except Exception: + redirect = self.failure_url + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'provider network "%s".') % providernet_id, + redirect=redirect) + self._providernet = providernet + return self._providernet + + def get_nova_data(self): + if not hasattr(self, "_providernet_nova"): + try: + providernet_id = self.kwargs['providernet_id'] + providernet_nova = api.nova.provider_network_get( + self.request, providernet_id) + except Exception: + redirect = self.failure_url + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'provider network "%s".') % providernet_id, + redirect=redirect) + + self._providernet_nova = providernet_nova + return self._providernet_nova + + def get_tabs(self, request, *args, **kwargs): + providernet = self.get_data() + nova_providernet = self.get_nova_data() + return self.tab_group_class( + request, providernet=providernet, + nova_providernet=nova_providernet, **kwargs) + + +class HostTopologyView(views.HorizonTemplateView): + template_name = 'admin/host_topology/index.html' + page_title = _("Provider Network Topology") + + def _has_permission(self, policy): + has_permission = True + # policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None) + policy_check = utils_settings.import_setting("POLICY_CHECK_FUNCTION") + + if policy_check: + has_permission = policy_check(policy, self.request) + + return has_permission + + def _quota_exceeded(self, quota): + usages = quotas.tenant_quota_usages(self.request) + available = usages[quota]['available'] + return available <= 0 + + def get_context_data(self, **kwargs): + context = super(HostTopologyView, self).get_context_data(**kwargs) + + context['launch_instance_allowed'] = self._has_permission( + (("compute", "compute:create"),)) + context['instance_quota_exceeded'] = self._quota_exceeded('instances') + return context + + +class JSONView(View): + + @property + def is_router_enabled(self): + network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}) + return network_config.get('enable_router', True) + + def add_resource_url(self, view, resources): + tenant_id = self.request.user.tenant_id + for resource in resources: + if (resource.get('tenant_id') + and tenant_id != resource.get('tenant_id')): + continue + resource['url'] = reverse(view, None, [str(resource['id'])]) + + def _check_router_external_port(self, ports, router_id, network_id): + for port in ports: + if (port['network_id'] == network_id + and port['device_id'] == router_id): + return True + return False + + def _get_alarms(self, request): + alarms = [] + try: + alarms = api.sysinv.alarm_list(request) + except Exception as ex: + exceptions.handle(ex) + + data = [a.to_dict() for a in alarms] + return data + + def _get_hosts(self, request): + hosts = [] + try: + hosts = api.sysinv.host_list(request) + except Exception as ex: + exceptions.handle(ex) + + data = [] + for host in hosts: + host_data = host.to_dict() + try: + host_data['ports'] = [ + p.to_dict() for p in + api.sysinv.host_port_list(request, host.uuid)] + + host_data['interfaces'] = [ + i.to_dict() for i in + api.sysinv.host_interface_list(request, host.uuid)] + + host_data['lldpneighbours'] = [ + n.to_dict() for n in + api.sysinv.host_lldpneighbour_list(request, host.uuid)] + + # Set the value for neighbours field for each port in the host. + # This will be referenced in Interfaces table + for p in host_data['ports']: + p['neighbours'] = \ + [n['port_identifier'] for n in + host_data['lldpneighbours'] + if n['port_uuid'] == p['uuid']] + + except Exception as ex: + exceptions.handle(ex) + + data.append(host_data) + return data + + def _get_pnets(self, request): + pnets = [] + try: + pnets = api.neutron.provider_network_list(request) + except Exception as ex: + exceptions.handle(ex) + data = [p.to_dict() for p in pnets] + return data + + def get(self, request, *args, **kwargs): + data = {'hosts': self._get_hosts(request), + 'networks': self._get_pnets(request), + 'alarms': self._get_alarms(request), } + json_string = json.dumps(data, ensure_ascii=False) + return HttpResponse(json_string, content_type='text/json') diff --git a/cgcs_dashboard/dashboards/admin/inventory/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/forms.py b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/forms.py new file mode 100644 index 00000000..3b18aef1 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/forms.py @@ -0,0 +1,400 @@ +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cgtsclient import exc +from django.core.urlresolvers import reverse # noqa +from django import shortcuts +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UpdateCpuFunctions(forms.SelfHandlingForm): + host = forms.CharField(label=_("host"), + required=False, + widget=forms.widgets.HiddenInput) + host_id = forms.CharField(label=_("host_id"), + required=False, + widget=forms.widgets.HiddenInput) + + platform = forms.CharField( + label=_("------------------------ Function ------------------------"), + required=False, + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + + platform_processor0 = forms.DynamicIntegerField( + label=_("# of Platform Physical Cores on Processor 0:"), + min_value=0, max_value=99, + required=False) + platform_processor1 = forms.DynamicIntegerField( + label=_("# of Platform Physical Cores on Processor 1:"), + min_value=0, max_value=99, + required=False) + platform_processor2 = forms.DynamicIntegerField( + label=_("# of Platform Physical Cores on Processor 2:"), + min_value=0, max_value=99, + required=False) + platform_processor3 = forms.DynamicIntegerField( + label=_("# of Platform Physical Cores on Processor 3:"), + min_value=0, max_value=99, + required=False) + + vswitch = forms.CharField( + label=_("------------------------ Function ------------------------"), + required=False, + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + num_cores_on_processor0 = forms.DynamicIntegerField( + label=_("# of vSwitch Physical Cores on Processor 0:"), + min_value=0, max_value=99, + required=False) + num_cores_on_processor1 = forms.DynamicIntegerField( + label=_("# of vSwitch Physical Cores on Processor 1:"), + min_value=0, max_value=99, + required=False) + num_cores_on_processor2 = forms.DynamicIntegerField( + label=_("# of vSwitch Physical Cores on Processor 2:"), + min_value=0, max_value=99, + required=False) + num_cores_on_processor3 = forms.DynamicIntegerField( + label=_("# of vSwitch Physical Cores on Processor 3:"), + min_value=0, max_value=99, + required=False) + + shared_vcpu = forms.CharField( + label=_("------------------------ Function ------------------------"), + required=False, + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + num_shared_on_processor0 = forms.DynamicIntegerField( + label=_("# of Shared Physical Cores on Processor 0:"), + min_value=0, max_value=99, + required=False) + num_shared_on_processor1 = forms.DynamicIntegerField( + label=_("# of Shared Physical Cores on Processor 1:"), + min_value=0, max_value=99, + required=False) + num_shared_on_processor2 = forms.DynamicIntegerField( + label=_("# of Shared Physical Cores on Processor 2:"), + min_value=0, max_value=99, + required=False) + num_shared_on_processor3 = forms.DynamicIntegerField( + label=_("# of Shared Physical Cores on Processor 3:"), + min_value=0, max_value=99, + required=False) + + failure_url = 'horizon:admin:inventory:detail' + + def __init__(self, *args, **kwargs): + super(UpdateCpuFunctions, self).__init__(*args, **kwargs) + + self.host = kwargs['initial']['host'] + + if kwargs['initial']['platform_processor0'] == 99: # No Processor + self.fields[ + 'platform_processor0'].widget = forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(0, 0) + self.fields['platform_processor0'].set_max_value( + avail_socket_cores) + self.fields[ + 'platform_processor0'].help_text = \ + "Processor 0 has %s physical cores." % avail_socket_cores + + if kwargs['initial']['platform_processor1'] == 99: # No Processor + self.fields[ + 'platform_processor1'].widget = forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(1, 0) + self.fields['platform_processor1'].set_max_value( + avail_socket_cores) + self.fields[ + 'platform_processor1'].help_text =\ + "Processor 1 has %s physical cores." % avail_socket_cores + + if kwargs['initial']['platform_processor2'] == 99: # No Processor + self.fields[ + 'platform_processor2'].widget = forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(2, 0) + self.fields['platform_processor2'].set_max_value( + avail_socket_cores) + self.fields[ + 'platform_processor2'].help_text = \ + "Processor 2 has %s physical cores." % avail_socket_cores + + if kwargs['initial']['platform_processor3'] == 99: # No Processor + self.fields[ + 'platform_processor3'].widget = forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(3, 0) + self.fields['platform_processor3'].set_max_value( + avail_socket_cores) + self.fields[ + 'platform_processor3'].help_text = \ + "Processor 3 has %s physical cores." % avail_socket_cores + + if 'compute' not in self.host.subfunctions: + self.fields['vswitch'].widget = forms.widgets.HiddenInput() + self.fields[ + 'num_cores_on_processor0'].widget = forms.widgets.HiddenInput() + self.fields[ + 'num_cores_on_processor1'].widget = forms.widgets.HiddenInput() + self.fields[ + 'num_cores_on_processor2'].widget = forms.widgets.HiddenInput() + self.fields[ + 'num_cores_on_processor3'].widget = forms.widgets.HiddenInput() + else: + if kwargs['initial'][ + 'num_cores_on_processor0'] == 99: # No Processor + self.fields[ + 'num_cores_on_processor0'].widget =\ + forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(0, 0) + self.fields[ + 'num_cores_on_processor0'].set_max_value( + avail_socket_cores) + self.fields[ + 'num_cores_on_processor0'].help_text = \ + "Processor 0 has %s physical cores." % avail_socket_cores + + if kwargs['initial'][ + 'num_cores_on_processor1'] == 99: # No Processor + self.fields[ + 'num_cores_on_processor1'].widget =\ + forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(1, 0) + self.fields[ + 'num_cores_on_processor1'].set_max_value( + avail_socket_cores) + self.fields[ + 'num_cores_on_processor1'].help_text =\ + "Processor 1 has %s physical cores." % avail_socket_cores + + if kwargs['initial'][ + 'num_cores_on_processor2'] == 99: # No Processor + self.fields[ + 'num_cores_on_processor2'].widget =\ + forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(2, 0) + self.fields[ + 'num_cores_on_processor2'].set_max_value( + avail_socket_cores) + self.fields[ + 'num_cores_on_processor2'].help_text =\ + "Processor 2 has %s physical cores." % avail_socket_cores + + if kwargs['initial'][ + 'num_cores_on_processor3'] == 99: # No Processor + self.fields[ + 'num_cores_on_processor3'].widget =\ + forms.widgets.HiddenInput() + else: + avail_socket_cores = self.host.physical_cores.get(3, 0) + self.fields[ + 'num_cores_on_processor3'].set_max_value( + avail_socket_cores) + self.fields[ + 'num_cores_on_processor3'].help_text =\ + "Processor 3 has %s physical cores." % avail_socket_cores + + for s in range(0, 4): + processor = 'num_shared_on_processor{0}'.format(s) + if ('compute' not in self.host.subfunctions or + kwargs['initial'][processor] == 99): # No Processor + self.fields[processor].widget = forms.widgets.HiddenInput() + else: + self.fields[processor].set_max_value(1) + self.fields[processor].help_text =\ + "Each processor can have at most one shared core." + + def clean(self): + cleaned_data = super(UpdateCpuFunctions, self).clean() + + # host_id = cleaned_data.get('host_id') + + try: + cleaned_data['platform_processor0'] = str( + cleaned_data['platform_processor0']) + cleaned_data['platform_processor1'] = str( + cleaned_data['platform_processor1']) + cleaned_data['platform_processor2'] = str( + cleaned_data['platform_processor2']) + cleaned_data['platform_processor3'] = str( + cleaned_data['platform_processor3']) + + cleaned_data['num_cores_on_processor0'] = str( + cleaned_data['num_cores_on_processor0']) + cleaned_data['num_cores_on_processor1'] = str( + cleaned_data['num_cores_on_processor1']) + cleaned_data['num_cores_on_processor2'] = str( + cleaned_data['num_cores_on_processor2']) + cleaned_data['num_cores_on_processor3'] = str( + cleaned_data['num_cores_on_processor3']) + + cleaned_data['num_shared_on_processor0'] = str( + cleaned_data['num_shared_on_processor0']) + cleaned_data['num_shared_on_processor1'] = str( + cleaned_data['num_shared_on_processor1']) + cleaned_data['num_shared_on_processor2'] = str( + cleaned_data['num_shared_on_processor2']) + cleaned_data['num_shared_on_processor3'] = str( + cleaned_data['num_shared_on_processor3']) + + num_platform_cores = {} + num_platform_cores[0] = cleaned_data.get('platform_processor0', + 'None') + num_platform_cores[1] = cleaned_data.get('platform_processor1', + 'None') + num_platform_cores[2] = cleaned_data.get('platform_processor2', + 'None') + num_platform_cores[3] = cleaned_data.get('platform_processor3', + 'None') + + num_vswitch_cores = {} + num_vswitch_cores[0] = cleaned_data.get('num_cores_on_processor0', + 'None') + num_vswitch_cores[1] = cleaned_data.get('num_cores_on_processor1', + 'None') + num_vswitch_cores[2] = cleaned_data.get('num_cores_on_processor2', + 'None') + num_vswitch_cores[3] = cleaned_data.get('num_cores_on_processor3', + 'None') + + num_shared_on_map = {} + num_shared_on_map[0] = cleaned_data.get('num_shared_on_processor0', + 'None') + num_shared_on_map[1] = cleaned_data.get('num_shared_on_processor1', + 'None') + num_shared_on_map[2] = cleaned_data.get('num_shared_on_processor2', + 'None') + num_shared_on_map[3] = cleaned_data.get('num_shared_on_processor3', + 'None') + + if ('None' in num_platform_cores.values() or + 'None' in num_vswitch_cores.values() or + 'None' in num_shared_on_map.values()): + raise forms.ValidationError(_("Invalid entry.")) + except Exception as e: + LOG.error(e) + raise forms.ValidationError(_("Invalid entry.")) + + # Since only vswitch is allowed to be modified + cleaned_data['function'] = 'vswitch' + # NOTE: shared_vcpu can be changed + + return cleaned_data + + def handle(self, request, data): + host_id = data['host_id'] + del data['host_id'] + del data['host'] + + try: + host = api.sysinv.host_get(self.request, host_id) + cpudata = {} + sharedcpudata = {} + platformcpudata = {} + for key, val in data.iteritems(): + if 'num_cores_on_processor' in key or 'function' in key: + if key not in self.fields: + cpudata[key] = val + elif not type(self.fields[key].widget) is\ + forms.widgets.HiddenInput: + cpudata[key] = val + if 'platform_processor' in key: + update_key = 'num_cores_on_processor' + key[-1:] + if key not in self.fields: + platformcpudata[update_key] = val + elif not type(self.fields[key].widget) is\ + forms.widgets.HiddenInput: + platformcpudata[update_key] = val + if 'num_shared_on_processor' in key: + key2 = key.replace('shared', 'cores') + if key not in self.fields: + sharedcpudata[key2] = val + elif not type(self.fields[key].widget) is\ + forms.widgets.HiddenInput: + sharedcpudata[key2] = val + + sharedcpudata['function'] = 'shared' + platformcpudata['function'] = 'platform' + + api.sysinv.host_cpus_modify(request, host.uuid, + platformcpudata, + cpudata, + sharedcpudata) + msg = _('CPU Assignments were successfully updated.') + LOG.debug(msg) + messages.success(request, msg) + return self.host.cpus + except exc.ClientException as ce: + # Display REST API error message on UI + messages.error(request, ce) + LOG.error(ce) + + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[host_id]) + return shortcuts.redirect(redirect) + except Exception as e: + LOG.exception(e) + msg = _('Failed to update CPU Assignments.') + LOG.info(msg) + redirect = reverse(self.failure_url, args=[host_id]) + exceptions.handle(request, msg, redirect=redirect) + + +class AddCpuProfile(forms.SelfHandlingForm): + host_id = forms.CharField(widget=forms.widgets.HiddenInput) + profilename = forms.CharField(label=_("Cpu Profile Name"), + required=True) + + failure_url = 'horizon:admin:inventory:detail' + + def __init__(self, *args, **kwargs): + super(AddCpuProfile, self).__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super(AddCpuProfile, self).clean() + # host_id = cleaned_data.get('host_id') + return cleaned_data + + def handle(self, request, data): + + cpuProfileName = data['profilename'] + try: + cpuProfile = api.sysinv.host_cpuprofile_create(request, **data) + msg = _( + 'Cpu Profile "%s" was successfully created.') % cpuProfileName + LOG.debug(msg) + messages.success(request, msg) + return cpuProfile + except exc.ClientException as ce: + # Display REST API error message on UI + messages.error(request, ce) + LOG.error(ce) + + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[data['host_id']]) + return shortcuts.redirect(redirect) + except Exception: + msg = _('Failed to create cpu profile "%s".') % cpuProfileName + LOG.info(msg) + redirect = reverse(self.failure_url, + args=[data['host_id']]) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/tables.py b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/tables.py new file mode 100644 index 00000000..71b3deca --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/tables.py @@ -0,0 +1,81 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse # noqa +from django import template +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables + +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.cpu_functions \ + import utils as cpufunctions_utils + +LOG = logging.getLogger(__name__) + + +class EditCpuFunctions(tables.LinkAction): + name = "editCpuFunctions" + verbose_name = _("Edit CPU Assignments") + url = "horizon:admin:inventory:editcpufunctions" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, datum=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id,)) + + def allowed(self, request, cpufunction=None): + host = self.table.kwargs['host'] + return host._administrative == 'locked' + + +class CreateCpuProfile(tables.LinkAction): + name = "createCpuProfile" + verbose_name = _("Create Cpu Profile") + url = "horizon:admin:inventory:addcpuprofile" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, datum=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id,)) + + def allowed(self, request, cpufunction=None): + return not api.sysinv.is_system_mode_simplex(request) + + +def get_function_name(datum): + if datum.allocated_function in cpufunctions_utils.CPU_TYPE_FORMATS: + return cpufunctions_utils.CPU_TYPE_FORMATS[datum.allocated_function] + return "unknown({})".format(datum.allocated_function) + + +def get_socket_cores(datum): + template_name = 'admin/inventory/cpu_functions/' \ + '_cpufunction_processorcores.html' + context = {"cpufunction": datum} + return template.loader.render_to_string(template_name, context) + + +class CpuFunctionsTable(tables.DataTable): + allocated_function = tables.Column(get_function_name, + verbose_name=_('Function')) + + socket_cores = tables.Column(get_socket_cores, + verbose_name=_('Processor Logical Cores')) + + def get_object_id(self, datum): + return unicode(datum.allocated_function) + + class Meta(object): + name = "cpufunctions" + verbose_name = _("CPU Assignments") + multi_select = False + table_actions = (CreateCpuProfile, EditCpuFunctions,) + row_actions = () diff --git a/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/utils.py b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/utils.py new file mode 100755 index 00000000..40625432 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/utils.py @@ -0,0 +1,238 @@ +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. + +from django.utils.translation import ugettext_lazy as _ + + +PLATFORM_CPU_TYPE = "Platform" +VSWITCH_CPU_TYPE = "Vswitch" +SHARED_CPU_TYPE = "Shared" +VMS_CPU_TYPE = "VMs" +NONE_CPU_TYPE = "None" + +CPU_TYPE_LIST = [PLATFORM_CPU_TYPE, VSWITCH_CPU_TYPE, + SHARED_CPU_TYPE, VMS_CPU_TYPE, + NONE_CPU_TYPE] + + +PLATFORM_CPU_TYPE_FORMAT = _("Platform") +VSWITCH_CPU_TYPE_FORMAT = _("vSwitch") +SHARED_CPU_TYPE_FORMAT = _("Shared") +VMS_CPU_TYPE_FORMAT = _("VMs") +NONE_CPU_TYPE_FORMAT = _("None") + +CPU_TYPE_FORMATS = {PLATFORM_CPU_TYPE: PLATFORM_CPU_TYPE_FORMAT, + VSWITCH_CPU_TYPE: VSWITCH_CPU_TYPE_FORMAT, + SHARED_CPU_TYPE: SHARED_CPU_TYPE_FORMAT, + VMS_CPU_TYPE: VMS_CPU_TYPE_FORMAT, + NONE_CPU_TYPE: NONE_CPU_TYPE_FORMAT} + + +class CpuFunction(object): + def __init__(self, function): + self.allocated_function = function + self.socket_cores = {} + self.socket_cores_number = {} + + +class CpuProfile(object): + class CpuConfigure(object): + def __init__(self): + self.platform = 0 + self.vswitch = 0 + self.shared = 0 + self.vms = 0 + self.numa_node = 0 + + # cpus is a list of icpu sorted by numa_node, core and thread + # if not sorted, provide nodes list so it can be sorted here + def __init__(self, cpus, nodes=None): + if nodes: + cpus = CpuProfile.sort_cpu_by_numa_node(cpus, nodes) + + cores = [] + + self.number_of_cpu = 0 + self.cores_per_cpu = 0 + self.hyper_thread = False + self.processors = [] + cur_processor = None + + for cpu in cpus: + key = '{0}-{1}'.format(cpu.numa_node, cpu.core) + if key not in cores: + cores.append(key) + else: + self.hyper_thread = True + continue + + if cur_processor is None \ + or cur_processor.numa_node != cpu.numa_node: + cur_processor = CpuProfile.CpuConfigure() + cur_processor.numa_node = cpu.numa_node + self.processors.append(cur_processor) + + if cpu.allocated_function == PLATFORM_CPU_TYPE: + cur_processor.platform += 1 + elif cpu.allocated_function == VSWITCH_CPU_TYPE: + cur_processor.vswitch += 1 + elif cpu.allocated_function == SHARED_CPU_TYPE: + cur_processor.shared += 1 + elif cpu.allocated_function == VMS_CPU_TYPE: + cur_processor.vms += 1 + + self.cores_per_cpu = len(cores) + self.number_of_cpu = len(self.processors) + + @staticmethod + def sort_cpu_by_numa_node(cpus, nodes): + newlist = [] + for node in nodes: + for cpu in cpus: + if cpu.numa_node == node.numa_node: + newlist.append(cpu) + return newlist + + +class HostCpuProfile(CpuProfile): + def __init__(self, personality, cpus, nodes=None): + super(HostCpuProfile, self).__init__(cpus, nodes) + self.personality = personality + + # see if a cpu profile is applicable to this host + def profile_applicable(self, profile): + if self.number_of_cpu == profile.number_of_cpu and \ + self.cores_per_cpu == profile.cores_per_cpu: + return self.check_profile_core_functions(profile) + else: + return False + + return not True + + def check_profile_core_functions(self, profile): + platform_cores = 0 + vswitch_cores = 0 + shared_cores = 0 + vm_cores = 0 + for cpu in profile.processors: + platform_cores += cpu.platform + vswitch_cores += cpu.vswitch + shared_cores += cpu.shared + vm_cores += cpu.vms + + result = True + if platform_cores == 0: + result = False + elif 'compute' in self.personality and vswitch_cores == 0: + result = False + elif 'compute' in self.personality and vm_cores == 0: + result = False + return result + + +def compress_range(c_list): + c_list.append(999) + c_list.sort() + c_sep = "" + c_item = "" + c_str = "" + pn = 0 + for n in c_list: + if not c_item: + c_item = "%s" % n + else: + if n > (pn + 1): + if int(pn) == int(c_item): + c_str = "%s%s%s" % (c_str, c_sep, c_item) + else: + c_str = "%s%s%s-%s" % (c_str, c_sep, c_item, pn) + c_sep = "," + c_item = "%s" % n + pn = n + return c_str + + +def restructure_host_cpu_data(host): + host.core_assignment = [] + if host.cpus: + host.cpu_model = host.cpus[0].cpu_model + host.sockets = len(host.nodes) + host.hyperthreading = "No" + host.physical_cores = {} + + core_assignment = {} + number_of_cores = {} + + for cpu in host.cpus: + if cpu.numa_node not in host.physical_cores: + host.physical_cores[cpu.numa_node] = 0 + if cpu.thread == 0: + host.physical_cores[cpu.numa_node] += 1 + elif cpu.thread > 0: + host.hyperthreading = "Yes" + + if cpu.allocated_function is None: + cpu.allocated_function = NONE_CPU_TYPE + + if cpu.allocated_function not in core_assignment: + core_assignment[cpu.allocated_function] = {} + number_of_cores[cpu.allocated_function] = {} + if cpu.numa_node not in core_assignment[cpu.allocated_function]: + core_assignment[cpu.allocated_function][cpu.numa_node] = [ + int(cpu.cpu)] + number_of_cores[cpu.allocated_function][cpu.numa_node] = 1 + else: + core_assignment[cpu.allocated_function][cpu.numa_node].append( + int(cpu.cpu)) + number_of_cores[cpu.allocated_function][cpu.numa_node] += 1 + + for f in CPU_TYPE_LIST: + cpufunction = CpuFunction(f) + if f in core_assignment: + host.core_assignment.append(cpufunction) + for s, cores in core_assignment[f].items(): + cpufunction.socket_cores[s] = compress_range(cores) + cpufunction.socket_cores_number[s] = number_of_cores[f][s] + else: + if (f == PLATFORM_CPU_TYPE or + (hasattr(host, 'subfunctions') and + 'compute' in host.subfunctions)): + if f != NONE_CPU_TYPE: + host.core_assignment.append(cpufunction) + for s in range(0, len(host.nodes)): + cpufunction.socket_cores[s] = "" + cpufunction.socket_cores_number[s] = 0 + + +def check_core_functions(personality, icpus): + platform_cores = 0 + vswitch_cores = 0 + shared_vcpu_cores = 0 + vm_cores = 0 + for cpu in icpus: + allocated_function = cpu.allocated_function + if allocated_function == PLATFORM_CPU_TYPE: + platform_cores += 1 + elif allocated_function == VSWITCH_CPU_TYPE: + vswitch_cores += 1 + elif allocated_function == SHARED_CPU_TYPE: + shared_vcpu_cores += 1 + elif allocated_function == VMS_CPU_TYPE: + vm_cores += 1 + + # No limiations for shared_vcpu cores + error_string = "" + if platform_cores == 0: + error_string = "There must be at least one" \ + " core for %s." % PLATFORM_CPU_TYPE_FORMAT + elif 'compute' in personality and vswitch_cores == 0: + error_string = "There must be at least one" \ + " core for %s." % VSWITCH_CPU_TYPE_FORMAT + elif 'compute' in personality and vm_cores == 0: + error_string = "There must be at least one" \ + " core for %s." % VMS_CPU_TYPE_FORMAT + return error_string diff --git a/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/views.py b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/views.py new file mode 100755 index 00000000..ead04e6a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/cpu_functions/views.py @@ -0,0 +1,193 @@ +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +import utils as icpu_utils + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.cpu_functions.forms \ + import AddCpuProfile +from openstack_dashboard.dashboards.admin.inventory.cpu_functions.forms \ + import UpdateCpuFunctions +from openstack_dashboard.dashboards.admin.inventory.cpu_functions \ + import utils as cpufunctions_utils + +LOG = logging.getLogger(__name__) + + +class UpdateCpuFunctionsView(forms.ModalFormView): + form_class = UpdateCpuFunctions + template_name = 'admin/inventory/cpu_functions/update.html' + context_object_name = 'cpufunctions' + success_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + host_id = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, host_id) + host.nodes = api.sysinv.host_node_list(self.request, host.uuid) + host.cpus = api.sysinv.host_cpu_list(self.request, host.uuid) + icpu_utils.restructure_host_cpu_data(host) + self._object = host + self._object.host_id = host_id + except Exception as e: + LOG.exception(e) + redirect = reverse("horizon:project:networks:detail", + args=(host_id)) + msg = _('Unable to retrieve port details') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = \ + super(UpdateCpuFunctionsView, self).get_context_data(**kwargs) + host = self._get_object() + context['host_id'] = host.host_id + return context + + def get_physical_core_count(self, host, cpufunc, socket): + value = cpufunc.socket_cores_number.get(socket, 0) + if host.hyperthreading.lower() == "yes": + value = value / 2 + return value + + def get_initial(self): + host = self._get_object() + + platform_processor0 = 99 # NO_PROCESSOR + platform_processor1 = 99 # NO_PROCESSOR + platform_processor2 = 99 # NO_PROCESSOR + platform_processor3 = 99 # NO_PROCESSOR + + num_cores_on_processor0 = 99 # NO_PROCESSOR + num_cores_on_processor1 = 99 # NO_PROCESSOR + num_cores_on_processor2 = 99 # NO_PROCESSOR + num_cores_on_processor3 = 99 # NO_PROCESSOR + + num_shared_on_processor0 = 99 # NO_PROCESSOR + num_shared_on_processor1 = 99 # NO_PROCESSOR + num_shared_on_processor2 = 99 # NO_PROCESSOR + num_shared_on_processor3 = 99 # NO_PROCESSOR + + for cpufunc in host.core_assignment: + if cpufunc.allocated_function == icpu_utils.PLATFORM_CPU_TYPE: + if host.sockets > 0: + platform_processor0 = self.get_physical_core_count( + host, cpufunc, 0) + if host.sockets > 1: + platform_processor1 = self.get_physical_core_count( + host, cpufunc, 1) + if host.sockets > 2: + platform_processor2 = self.get_physical_core_count( + host, cpufunc, 2) + if host.sockets > 3: + platform_processor3 = self.get_physical_core_count( + host, cpufunc, 3) + + elif cpufunc.allocated_function == icpu_utils.VSWITCH_CPU_TYPE: + if host.sockets > 0: + num_cores_on_processor0 = \ + self.get_physical_core_count(host, cpufunc, 0) + if host.sockets > 1: + num_cores_on_processor1 = \ + self.get_physical_core_count(host, cpufunc, 1) + if host.sockets > 2: + num_cores_on_processor2 = \ + self.get_physical_core_count(host, cpufunc, 2) + if host.sockets > 3: + num_cores_on_processor3 = \ + self.get_physical_core_count(host, cpufunc, 3) + + elif cpufunc.allocated_function == icpu_utils.SHARED_CPU_TYPE: + if host.sockets > 0: + num_shared_on_processor0 = \ + self.get_physical_core_count(host, cpufunc, 0) + if host.sockets > 1: + num_shared_on_processor1 = \ + self.get_physical_core_count(host, cpufunc, 1) + if host.sockets > 2: + num_shared_on_processor2 = \ + self.get_physical_core_count(host, cpufunc, 2) + if host.sockets > 3: + num_shared_on_processor3 = \ + self.get_physical_core_count(host, cpufunc, 3) + + return {'host': host, + 'host_id': host.host_id, + 'platform': icpu_utils.PLATFORM_CPU_TYPE_FORMAT, + 'platform_processor0': platform_processor0, + 'platform_processor1': platform_processor1, + 'platform_processor2': platform_processor2, + 'platform_processor3': platform_processor3, + 'vswitch': icpu_utils.VSWITCH_CPU_TYPE_FORMAT, + 'num_cores_on_processor0': num_cores_on_processor0, + 'num_cores_on_processor1': num_cores_on_processor1, + 'num_cores_on_processor2': num_cores_on_processor2, + 'num_cores_on_processor3': num_cores_on_processor3, + 'shared_vcpu': icpu_utils.SHARED_CPU_TYPE_FORMAT, + 'num_shared_on_processor0': num_shared_on_processor0, + 'num_shared_on_processor1': num_shared_on_processor1, + 'num_shared_on_processor2': num_shared_on_processor2, + 'num_shared_on_processor3': num_shared_on_processor3} + + +class AddCpuProfileView(forms.ModalFormView): + form_class = AddCpuProfile + template_name = 'admin/inventory/cpu_functions/createprofile.html' + success_url = 'horizon:admin:inventory:detail' + failure_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['host_id'],)) + + def get_myhost_data(self): + if not hasattr(self, "_host"): + host_id = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, host_id) + host.nodes = api.sysinv.host_node_list(self.request, host.uuid) + host.cpus = api.sysinv.host_cpu_list(self.request, host.uuid) + icpu_utils.restructure_host_cpu_data(host) + except Exception: + redirect = reverse('horizon:admin:inventory:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'host "%s".') % host_id, + redirect=redirect) + self._host = host + return self._host + + def get_context_data(self, **kwargs): + context = super(AddCpuProfileView, self).get_context_data(**kwargs) + context['host_id'] = self.kwargs['host_id'] + context['host'] = self.get_myhost_data() + context['cpu_formats'] = cpufunctions_utils.CPU_TYPE_FORMATS + return context + + def get_initial(self): + initial = super(AddCpuProfileView, self).get_initial() + initial['host_id'] = self.kwargs['host_id'] + return initial diff --git a/cgcs_dashboard/dashboards/admin/inventory/devices/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/devices/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/devices/forms.py b/cgcs_dashboard/dashboards/admin/inventory/devices/forms.py new file mode 100755 index 00000000..a2da6267 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/devices/forms.py @@ -0,0 +1,78 @@ +# +# Copyright (c) 2014-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UpdateDevice(forms.SelfHandlingForm): + + host_id = forms.CharField(label=_("host_id"), + required=False, + widget=forms.widgets.HiddenInput) + + uuid = forms.CharField(label=_("uuid"), + required=False, + widget=forms.widgets.HiddenInput) + + device_name = forms.CharField(label=_("Device Name"), + required=False, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + + pciaddr = forms.CharField(label=_("Device Address"), + required=False, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + + name = forms.CharField(label=_("Name"), + required=False, + widget=forms.TextInput()) + + enabled = forms.BooleanField(label=_("Enabled"), + required=False, + widget=forms.CheckboxInput()) + + failure_url = 'horizon:admin:inventory:detail' + + def clean(self): + data = super(UpdateDevice, self).clean() + if isinstance(data['enabled'], bool): + data['enabled'] = 'True' if data['enabled'] else 'False' + return data + + def handle(self, request, data): + name = data['name'] + uuid = data['uuid'] + + try: + p = {} + p['name'] = name + p['enabled'] = str(data['enabled']) + device = api.sysinv.host_device_update(request, uuid, **p) + msg = _('device "%s" was successfully updated.') % name + LOG.debug(msg) + messages.success(request, msg) + return device + except Exception as exc: + msg = _('Failed to update device "%(n)s" (%(e)s).') % ({'n': name, + 'e': exc}) + LOG.info(msg) + redirect = reverse(self.failure_url, args=[data['host_id']]) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/devices/tables.py b/cgcs_dashboard/dashboards/admin/inventory/devices/tables.py new file mode 100755 index 00000000..173ca30b --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/devices/tables.py @@ -0,0 +1,144 @@ +# +# Copyright (c) 2014-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables + +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class EditDevice(tables.LinkAction): + name = "update" + verbose_name = _("Edit Device") + url = "horizon:admin:inventory:editdevice" + classes = ("ajax-modal", "btn-edit") + + def get_link_url(self, device=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id, device.uuid)) + + def allowed(self, request, datum): + host = self.table.kwargs['host'] + return (host._administrative == 'locked' and + api.sysinv.SUBFUNCTIONS_COMPUTE in host.subfunctions) + + +def get_viewdevice_link_url(device): + return reverse("horizon:admin:inventory:viewdevice", + args=(device.host_id, device.uuid)) + + +class DevicesTable(tables.DataTable): + """Devices Table per host under Host Tab""" + + name = tables.Column('name', + verbose_name=_('Name'), + link=get_viewdevice_link_url) + address = tables.Column('pciaddr', + verbose_name=_('Address')) + device_id = tables.Column('pdevice_id', + verbose_name=_('Device Id')) + device_name = tables.Column('pdevice', + verbose_name=_('Device Name')) + numa_node = tables.Column('numa_node', + verbose_name=_('Numa Node')) + enabled = tables.Column('enabled', + verbose_name=_('Enabled')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.name + + class Meta(object): + name = "devices" + verbose_name = _("Devices") + multi_select = False + row_actions = (EditDevice,) + + +class UsageTable(tables.DataTable): + """Detail usage table for a device under Device Usage tab""" + + host = tables.Column('host', + verbose_name=_('Host')) + pci_pfs_configured = tables.Column('pci_pfs_configured', + verbose_name=_('PFs configured')) + pci_pfs_used = tables.Column('pci_pfs_configured', + verbose_name=_('PFs used')) + pci_vfs_configured = tables.Column('pci_vfs_configured', + verbose_name=_('VFs configured')) + pci_vfs_used = tables.Column('pci_vfs_used', + verbose_name=_('VFs used')) + + def get_object_id(self, datum): + return unicode(datum.id) + + def get_object_display(self, datum): + return datum.host + + class Meta(object): + name = "usage" + verbose_name = _("Usage") + multi_select = False + + +def get_viewusage_link_url(usage): + return reverse("horizon:admin:inventory:viewusage", + args=(usage.device_id,)) + + +class DeviceUsageTable(tables.DataTable): + """Device Usage table for all devices (i.e Device Usage tab)""" + + device_name = tables.Column('device_name', + link=get_viewusage_link_url, + verbose_name=_('PCI Alias')) + + description = tables.Column('description', verbose_name=_('Description')) + + device_id = tables.Column('device_id', + verbose_name=_('Device Id')) + + vendor_id = tables.Column('vendor_id', + verbose_name=_('Vendor Id')) + + class_id = tables.Column('class_id', + verbose_name=_('Class Id')) + + pci_pfs_configured = tables.Column('pci_pfs_configured', + verbose_name=_("PFs configured")) + + pci_pfs_used = tables.Column('pci_pfs_used', + verbose_name=_("PFs used")) + + pci_vfs_configured = tables.Column('pci_vfs_configured', + verbose_name=_("VFs configured")) + + pci_vfs_used = tables.Column('pci_vfs_used', + verbose_name=_("VFs used")) + + def get_object_id(self, datum): + return unicode(datum.device_id) + + def get_object_display(self, datum): + return datum.device_name + + class Meta(object): + name = "deviceusage" + verbose_name = _("Device Usage") diff --git a/cgcs_dashboard/dashboards/admin/inventory/devices/views.py b/cgcs_dashboard/dashboards/admin/inventory/devices/views.py new file mode 100755 index 00000000..b0aa45ca --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/devices/views.py @@ -0,0 +1,169 @@ +# +# Copyright (c) 2014-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon.utils import memoized +from horizon import views +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.devices.forms import \ + UpdateDevice +from openstack_dashboard.dashboards.admin.inventory.devices.tables import \ + UsageTable + +LOG = logging.getLogger(__name__) + + +class UpdateView(forms.ModalFormView): + form_class = UpdateDevice + template_name = 'admin/inventory/devices/update.html' + context_object_name = 'device' + success_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + device_uuid = self.kwargs['device_uuid'] + host_id = self.kwargs['host_id'] + try: + self._object = api.sysinv.host_device_get(self.request, + device_uuid) + self._object.host_id = host_id + except Exception: + redirect = reverse("horizon:admin:inventory:detail", + args=(host_id)) + msg = _('Unable to retrieve device details') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + device = self._get_object() + context['device_uuid'] = device.uuid + context['host_id'] = device.host_id + return context + + def get_initial(self): + device = self._get_object() + enabled = device.enabled + if isinstance(enabled, basestring): + if enabled.lower() == 'false': + enabled = False + elif enabled.lower() == 'true': + enabled = True + return {'name': device.name, + 'enabled': enabled, + 'pciaddr': device.pciaddr, + 'device_id': device.pdevice_id, + 'device_name': device.pdevice, + 'host_id': device.host_id, + 'uuid': device.uuid} + + +class DetailView(views.HorizonTemplateView): + template_name = 'admin/inventory/devices/detail.html' + page_title = '{{ device.name }}' + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + device_uuid = self.kwargs['device_uuid'] + host_id = self.kwargs['host_id'] + try: + self._object = api.sysinv.host_device_get(self.request, + device_uuid) + self._object.host_id = host_id + + except Exception: + redirect = reverse("horizon:admin:inventory:detail", + args=(self.kwargs['host_id'],)) + msg = _('Unable to retrieve device details') + exceptions.handle(self.request, msg, redirect=redirect) + + return self._object + + @memoized.memoized_method + def get_hostname(self, host_uuid): + try: + host = api.sysinv.host_get(self.request, host_uuid) + except Exception: + host = {} + msg = _('Unable to retrieve hostname details.') + exceptions.handle(self.request, msg) + return host.hostname + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + device = self._get_object() + + hostname = self.get_hostname(device.host_id) + host_nav = hostname or "Unprovisioned Node" + breadcrumb = [ + (host_nav, reverse('horizon:admin:inventory:detail', + args=(device.host_id,))), + (_("Devices"), None) + ] + context["custom_breadcrumb"] = breadcrumb + + context['device_uuid'] = device.uuid + context['host_id'] = device.host_id + context['device'] = device + return context + + +class UsageView(tables.MultiTableView): + table_classes = (UsageTable, ) + template_name = 'admin/inventory/devices/usage.html' + + def _handle_exception(self, device_id): + redirect = reverse("horizon:admin:inventory:index") + msg = _('Unable to retrieve device usage for %s') % device_id + exceptions.handle(self.request, msg, redirect=redirect) + + def get_usage_data(self, *args, **kwargs): + if not hasattr(self, "_detail_object"): + dev_id = self.kwargs['device_id'] + try: + _object = api.nova.get_detail_usage(self.request, dev_id) + _object.sort(key=lambda f: (f.host)) + id = 0 + for u in _object: + id += 1 + u.id = id + self._detail_object = _object + + except Exception: + self._handle_exception(dev_id) + + return self._detail_object + + def _get_device_usage(self, *args, **kwargs): + if not hasattr(self, "_usage_object"): + dev_id = self.kwargs['device_id'] + try: + _object = api.nova.get_device_usage(self.request, dev_id) + self._usage_object = _object + except Exception: + self._handle_exception(dev_id) + + return self._usage_object + + def get_context_data(self, **kwargs): + context = super(UsageView, self).get_context_data(**kwargs) + context['device_usage'] = self._get_device_usage() + return context diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/forms.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/forms.py new file mode 100755 index 00000000..690ecee3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/forms.py @@ -0,0 +1,69 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse +from django import shortcuts +from django.utils.translation import ugettext_lazy as _ +import netaddr + +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class CreateAddress(forms.SelfHandlingForm): + host_id = forms.CharField(widget=forms.HiddenInput()) + interface_id = forms.CharField(widget=forms.HiddenInput()) + success_url = 'horizon:admin:inventory:viewinterface' + failure_url = 'horizon:admin:inventory:viewinterface' + + ip_address = forms.IPField( + label=_("IP Address"), + required=True, + initial="", + help_text=_("IP interface address in CIDR format " + "(e.g. 192.168.0.2/24, 2001:DB8::/48"), + version=forms.IPv4 | forms.IPv6, + mask=True) + + def handle(self, request, data): + try: + ip_address = netaddr.IPNetwork(data['ip_address']) + body = {'interface_uuid': data['interface_id'], + 'address': str(ip_address.ip), + 'prefix': ip_address.prefixlen} + address = api.sysinv.address_create(request, **body) + msg = (_('Address %(address)s/%(prefix)s was ' + 'successfully created') % body) + messages.success(request, msg) + return address + except Exception as e: + # Allow REST API error message to appear on UI + messages.error(request, e) + LOG.error(e) + # Redirect to failure page + redirect = reverse(self.failure_url, + args=(data['host_id'], data['interface_id'])) + return shortcuts.redirect(redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/tables.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/tables.py new file mode 100755 index 00000000..0c8d2df2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/tables.py @@ -0,0 +1,130 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + +ALLOWED_INTERFACE_TYPES = ['infra', 'data', 'control'] + + +class DeleteAddress(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Address", + u"Delete Addresses", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Address", + u"Deleted Addresses", + count + ) + + def get_redirect_url(self): + host_id = self.table.kwargs['host_id'] + interface_id = self.table.kwargs['interface_id'] + return reverse('horizon:admin:inventory:viewinterface', + args=[host_id, interface_id]) + + def delete(self, request, obj_id): + try: + api.sysinv.address_delete(request, obj_id) + except Exception: + exceptions.handle(request, redirect=self.get_redirect_url()) + + +class CreateAddress(tables.LinkAction): + name = "create" + verbose_name = _("Create Address") + url = "horizon:admin:inventory:addaddress" + classes = ("ajax-modal",) + icon = "plus" + + def get_link_url(self, datum=None): + interface_id = self.table.kwargs['interface_id'] + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id, interface_id)) + + def allowed(self, request, datum=None): + interface = self.table.get_interface() + supported = interface.networktype.split(',') + if not interface: + return False + if any(t in supported for t in ALLOWED_INTERFACE_TYPES): + return True + if interface.ipv4_mode in ['static']: + return True + if interface.ipv6_mode in ['static']: + return True + return False + + +def get_address_column(address): + ip_address = getattr(address, 'address') + prefix = getattr(address, 'prefix') + return ip_address + '/' + str(prefix) + + +class AddressTable(tables.DataTable): + address = tables.Column(get_address_column, + verbose_name=_("Address")) + enable_dad = tables.Column("enable_dad", + verbose_name=_("DAD")) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return ("%(address)s/%(prefix)s" % + {'address': datum.address, + 'prefix': datum.prefix}) + + class Meta(object): + name = "addresses" + verbose_name = _("Address List") + table_actions = (CreateAddress, DeleteAddress) + row_actions = (DeleteAddress,) + + def get_interface(self): + if not hasattr(self, "_interface"): + try: + interface_id = self.kwargs["interface_id"] + self._interface = api.sysinv.host_interface_get( + self.request, interface_id) + except Exception: + redirect = reverse(self.failure_url, + args=(self.kwargs['host_id'], + self.kwargs['interface_id'],)) + msg = _("Unable to retrieve interface details.") + exceptions.handle(self.request, msg, redirect=redirect) + return + return self._interface + + def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs): + super(AddressTable, self).__init__( + request, data=data, needs_form_wrapper=needs_form_wrapper, + **kwargs) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/views.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/views.py new file mode 100755 index 00000000..d50166a5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/address/views.py @@ -0,0 +1,51 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import logging + +from django.core.urlresolvers import reverse # noqa + +from horizon import forms +from openstack_dashboard.dashboards.admin.inventory.interfaces.address import \ + forms as address_forms + +LOG = logging.getLogger(__name__) + + +class CreateView(forms.ModalFormView): + form_class = address_forms.CreateAddress + template_name = 'admin/inventory/interfaces/address/create.html' + success_url = 'horizon:admin:inventory:viewinterface' + failure_url = 'horizon:admin:inventory:viewinterface' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'], + self.kwargs['interface_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['host_id'], + self.kwargs['interface_id'],)) + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + context['interface_id'] = self.kwargs['interface_id'] + context['host_id'] = self.kwargs['host_id'] + return context + + def get_initial(self): + return {'interface_id': self.kwargs['interface_id'], + 'host_id': self.kwargs['host_id']} diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/forms.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/forms.py new file mode 100755 index 00000000..525832da --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/forms.py @@ -0,0 +1,886 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + + +import logging + +from compiler.ast import flatten +import netaddr + +from cgtsclient import exc +from django.core.urlresolvers import reverse # noqa +from django import shortcuts +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +def _get_ipv4_pool_choices(pools): + choices = [] + for p in pools: + address = netaddr.IPAddress(p.network) + if address.version == 4: + choices.append((p.uuid, p.name)) + return choices + + +def _get_ipv6_pool_choices(pools): + choices = [] + for p in pools: + address = netaddr.IPAddress(p.network) + if address.version == 6: + choices.append((p.uuid, p.name)) + return choices + + +class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple): + """Custom checkbox select widget that will render a text string + + with an hidden input if there are no choices. + + """ + + def __init__(self, attrs=None, choices=(), empty_value=''): + super(CheckboxSelectMultiple, self).__init__(attrs, choices) + self.empty_value = empty_value + + def render(self, name, value, attrs=None, choices=()): + if self.choices: + return super(CheckboxSelectMultiple, self).render(name, value, + attrs, choices) + else: + hi = forms.HiddenInput(self.attrs) + hi.is_hidden = False # ensure text is rendered + return mark_safe(self.empty_value + hi.render(name, None, attrs)) + + +class MultipleChoiceField(forms.MultipleChoiceField): + """Custom multiple choice field that only validates + + if a value was provided. + + """ + + def valid_value(self, value): + if not self.required and not value: + return True + return super(MultipleChoiceField, self).valid_value(value) + + +class AddInterfaceProfile(forms.SelfHandlingForm): + host_id = forms.CharField(widget=forms.widgets.HiddenInput) + profilename = forms.CharField(label=_("Interface Profile Name"), + required=True) + + failure_url = 'horizon:admin:inventory:detail' + + def __init__(self, *args, **kwargs): + super(AddInterfaceProfile, self).__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super(AddInterfaceProfile, self).clean() + # host_id = cleaned_data.get('host_id') + # interfaceProfileName = cleaned_data.get('hostname') + + return cleaned_data + + def handle(self, request, data): + host_id = data['host_id'] + interfaceProfileName = data['profilename'] + try: + interfaceProfile = api.sysinv.host_interfaceprofile_create(request, + **data) + msg = _( + 'Interface Profile "%s" was ' + 'successfully created.') % interfaceProfileName + LOG.debug(msg) + messages.success(request, msg) + return interfaceProfile + except exc.ClientException as ce: + # Allow REST API error message to appear on UI + messages.error(request, ce) + LOG.error(ce) + + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[host_id]) + return shortcuts.redirect(redirect) + except Exception: + msg = _( + 'Failed to create interface' + ' profile "%s".') % interfaceProfileName + LOG.info(msg) + redirect = reverse(self.failure_url, + args=[data['host_id']]) + exceptions.handle(request, msg, redirect=redirect) + + +class AddInterface(forms.SelfHandlingForm): + NETWORK_TYPE_CHOICES = ( + ('none', _("none")), + ('mgmt', _("mgmt")), + ('oam', _("oam")), + ('data', _("data")), + ('data-external', _("data-external")), + ('control', _("control")), + ('infra', _("infra")), + ('pxeboot', _("pxeboot")), + ) + + INTERFACE_TYPE_CHOICES = ( + (None, _("")), + ('ethernet', _("ethernet")), + ('ae', _("aggregated ethernet")), + ('vlan', _("vlan")), + ) + + id = forms.CharField(widget=forms.widgets.HiddenInput) + + networktype = forms.MultipleChoiceField( + label=_("Network Type"), + help_text=_("Note: The network type of an interface cannot be changed " + "without first being reset back to 'none'"), + required=True, + widget=forms.CheckboxSelectMultiple( + attrs={ + 'class': 'switchable', + 'data-slug': 'network_type'})) + + sriov_numvfs = forms.IntegerField( + label=_("Virtual Functions"), + required=False, + min_value=0, + help_text=_("Virtual Functions for pci-sriov."), + widget=forms.TextInput( + attrs={ + 'class': 'switched', + 'data-switch-on': 'network_type', + 'data-slug': 'num_vfs', + 'data-network_type-pci-sriov': 'Num VFs'})) + + sriov_totalvfs = forms.IntegerField( + label=_("Maximum Virtual Functions"), + required=False, + widget=forms.widgets.TextInput( + attrs={ + 'class': 'switched', + 'readonly': 'readonly', + 'data-switch-on': 'network_type', + 'data-network_type-pci-sriov': 'Max VFs'})) + + iftypedata = forms.ChoiceField( + label=_("Interface Type"), + choices=INTERFACE_TYPE_CHOICES, + widget=forms.HiddenInput) + + def __init__(self, *args, **kwargs): + super(UpdateInterface, self).__init__(*args, **kwargs) + + networktype_val = kwargs['initial']['networktype'] + host_uuid = kwargs['initial']['ihost_uuid'] + + # Get the SDN configuration + sdn_enabled = kwargs['initial']['sdn_enabled'] + sdn_l3_mode = kwargs['initial']['sdn_l3_mode_enabled'] + + this_interface_id = kwargs['initial']['id'] + + iftype_val = kwargs['initial']['iftype'] + if 'mgmt' in networktype_val: + self.fields['aemode'].choices = self.MGMT_AE_MODE_CHOICES + + else: + self.fields['aemode'].choices = self.AE_MODE_CHOICES + + # Populate Address Pool selections + pools = api.sysinv.address_pool_list(self.request) + self.fields['ipv4_pool'].choices = _get_ipv4_pool_choices(pools) + self.fields['ipv6_pool'].choices = _get_ipv6_pool_choices(pools) + self.fields['ipv4_pool'].initial = kwargs['initial'].get('ipv4_pool') + self.fields['ipv6_pool'].initial = kwargs['initial'].get('ipv6_pool') + + # Setting field to read-only doesn't actually work so we're making + # it disabled. This has the effect of not allowing the data through + # to the form submission, so we require a hidden field to carry the + # actual value through (iftype data) + self.fields['iftype'].widget.attrs['disabled'] = 'disabled' + self.fields['iftype'].required = False + self.fields['iftype'].choices = self.INTERFACE_TYPE_CHOICES + self.fields['iftypedata'].initial = kwargs['initial'].get('iftype') + self.fields['iftype'].initial = kwargs['initial'].get('iftype') + + # Load the networktype choices + networktype_choices = [] + used_choices = [] + if networktype_val: + for network in networktype_val: + label = "{}".format(network) + net_type = (str(network), label) + used_choices.append(str(network)) + networktype_choices.append(net_type) + else: + label = "{}".format("none") + net_type = ("none", label) + networktype_choices.append(net_type) + used_choices.append("none") + + # if SDN L3 mode is enabled, then we may allow + # updating an interface network type to 'data-external' + data_choices = ['data', 'control'] + if (sdn_enabled and sdn_l3_mode): + data_choices.append('data-external') + + if iftype_val == 'ethernet': + choices_list = ['none', 'infra', 'oam', 'mgmt', 'pci-passthrough', + data_choices, 'pci-sriov', 'pxeboot'] + elif iftype_val == 'ae': + choices_list = ['none', 'infra', 'oam', 'mgmt', + data_choices, 'pxeboot'] + else: + choices_list = ['infra', 'oam', 'mgmt', data_choices] + + choices_list = flatten(choices_list) + + for choice in choices_list: + if choice not in used_choices: + label = "{}".format(choice) + net_type = (str(choice), label) + networktype_choices.append(net_type) + + self.fields['networktype'].choices = networktype_choices + if not networktype_val: + del kwargs['initial']['networktype'] + self.fields['networktype'].initial = ('none', 'none') + + # Get the total possible number of VFs for SRIOV network type + port_list = api.sysinv.host_port_list(self.request, + host_uuid) + for p in port_list: + if p.interface_uuid == this_interface_id: + if p.sriov_totalvfs: + self.fields['sriov_totalvfs'].initial = p.sriov_totalvfs + else: + self.fields['sriov_totalvfs'].initial = 0 + break + + initial_numvfs = kwargs['initial']['sriov_numvfs'] + if initial_numvfs: + self.fields['sriov_numvfs'].initial = initial_numvfs + else: + self.fields['sriov_numvfs'].initial = 0 + + def clean(self): + cleaned_data = super(UpdateInterface, self).clean() + + cleaned_data['iftype'] = cleaned_data.get('iftypedata') + cleaned_data.pop('iftypedata', None) + + return cleaned_data + + def handle(self, request, data): + host_id = data['host_id'] + interface_id = data['id'] + host_uuid = data['ihost_uuid'] + + try: + if data['ports']: + del data['uses'] + else: + uses = data['uses'][:] + data['usesmodify'] = ','.join(uses) + del data['ports'] + del data['uses'] + + del data['id'] + del data['host_id'] + del data['ihost_uuid'] + + if not data['vlan_id'] or data['iftype'] != 'vlan': + del data['vlan_id'] + else: + data['vlan_id'] = unicode(data['vlan_id']) + + data['imtu'] = unicode(data['imtu']) + + if data['iftype'] != 'ae': + del data['txhashpolicy'] + del data['aemode'] + elif data['aemode'] == 'active_standby': + del data['txhashpolicy'] + + if 'none' in data['networktype']: + avail_port_list = api.sysinv.host_port_list( + self.request, host_uuid) + current_interface = api.sysinv.host_interface_get( + self.request, interface_id) + if data['iftype'] != 'ae' or data['iftype'] != 'vlan': + for p in avail_port_list: + if p.interface_uuid == current_interface.uuid: + data['ifname'] = p.get_port_display_name() + break + + if any(nt in ['data', 'data-external'] for nt in + [str(current_interface.networktype).split(",")]): + data['providernetworks'] = 'none' + + if not data['providernetworks']: + del data['providernetworks'] + + if 'sriov_numvfs' in data: + data['sriov_numvfs'] = unicode(data['sriov_numvfs']) + + # Explicitly set iftype when user selects pci-pt or pci-sriov + network_type = \ + flatten(list(nt) for nt in self.fields['networktype'].choices) + if 'pci-passthrough' in network_type or \ + ('pci-sriov' in network_type and data['sriov_numvfs']): + current_interface = api.sysinv.host_interface_get( + self.request, interface_id) + if current_interface.iftype != 'ethernet': + # Only ethernet interfaces can be pci-sriov + msg = _('pci-passthrough or pci-sriov can only' + ' be set on ethernet interfaces') + messages.error(request, msg) + LOG.error(msg) + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[host_id]) + return shortcuts.redirect(redirect) + else: + data['iftype'] = current_interface.iftype + + del data['sriov_totalvfs'] + if 'pci-sriov' not in data['networktype']: + del data['sriov_numvfs'] + + if data['networktype']: + data['networktype'] = ",".join(data['networktype']) + + interface = api.sysinv.host_interface_update(request, interface_id, + **data) + + # FIXME: this should be done under + # the interface update API of sysinv + # Update Ports' iinterface_uuid attribute + # port_list = api.sysinv.host_port_list(request, host_uuid) + # for p in port_list: + # if p.uuid in ports: + # pdata = { 'interface_uuid' : interface.uuid } + # api.sysinv.host_port_update(request, p.uuid, **pdata) + # elif p.interface_uuid == interface.uuid: + # pdata = { 'interface_uuid' : '0' } + # api.sysinv.host_port_update(request, p.uuid, **pdata) + + msg = _('Interface "%s" was' + ' successfully updated.') % data['ifname'] + LOG.debug(msg) + messages.success(request, msg) + return interface + + except exc.ClientException as ce: + # Allow REST API error message to appear on UI + messages.error(request, ce) + LOG.error(ce) + + # Redirect to failure page + redirect = reverse(self.failure_url, args=[host_id]) + return shortcuts.redirect(redirect) + + except Exception: + msg = _('Failed to update interface "%s".') % data['ifname'] + LOG.info(msg) + redirect = reverse(self.failure_url, args=[host_id]) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/forms.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/forms.py new file mode 100755 index 00000000..175cab8c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/forms.py @@ -0,0 +1,86 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse +from django import shortcuts +from django.utils.translation import ugettext_lazy as _ +import netaddr + +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class CreateRoute(forms.SelfHandlingForm): + + host_id = forms.CharField(widget=forms.HiddenInput()) + interface_id = forms.CharField(widget=forms.HiddenInput()) + success_url = 'horizon:admin:inventory:viewinterface' + failure_url = 'horizon:admin:inventory:viewinterface' + + network = forms.IPField( + label=_("Network Address"), + required=True, + initial="", + help_text=_("IP network address in CIDR format " + "(e.g. 192.168.0.0/24, 2001:DB8::/48"), + version=forms.IPv4 | forms.IPv6, + mask=True) + + gateway = forms.IPField( + label=_("Gateway Address"), + required=True, + initial="", + help_text=_("Gateway IP address " + "(e.g. 192.168.0.1/24, 2001:DB8::1/48"), + version=forms.IPv4 | forms.IPv6, + mask=False) + + metric = forms.IntegerField( + label=_("Route Metric"), + initial="1", + required=True) + + def handle(self, request, data): + try: + ip_network = netaddr.IPNetwork(data['network']) + body = {'interface_uuid': data['interface_id'], + 'network': str(ip_network.ip), + 'prefix': ip_network.prefixlen, + 'gateway': data['gateway'], + 'metric': data['metric']} + route = api.sysinv.route_create(request, **body) + msg = (_('Route to %(network)s/%(prefix)s via %(gateway)s was ' + 'successfully created') % body) + messages.success(request, msg) + return route + except Exception as e: + # Allow REST API error message to appear on UI + messages.error(request, e) + LOG.error(e) + # Redirect to failure page + redirect = reverse(self.failure_url, + args=(data['host_id'], data['interface_id'])) + return shortcuts.redirect(redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/tables.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/tables.py new file mode 100755 index 00000000..f8ad881f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/tables.py @@ -0,0 +1,130 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class DeleteRoute(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Route", + u"Delete Routes", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Route", + u"Deleted Routes", + count + ) + + def get_redirect_url(self): + host_id = self.table.kwargs['host_id'] + interface_id = self.table.kwargs['interface_id'] + return reverse('horizon:admin:inventory:viewinterface', + args=[host_id, interface_id]) + + def delete(self, request, obj_id): + try: + api.sysinv.route_delete(request, obj_id) + except Exception: + exceptions.handle(request, redirect=self.get_redirect_url()) + + +class CreateRoute(tables.LinkAction): + name = "create" + verbose_name = _("Create Route") + url = "horizon:admin:inventory:addroute" + classes = ("ajax-modal",) + icon = "plus" + + def get_link_url(self, datum=None): + interface_id = self.table.kwargs['interface_id'] + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id, interface_id)) + + def allowed(self, request, datum=None): + interface = self.table.get_interface() + if not interface: + return False + if interface.networktype not in ['data', 'control']: + return False + if interface.ipv4_mode in ['static']: + return True + if interface.ipv6_mode in ['static']: + return True + return False + + +def get_network_column(route): + network = getattr(route, 'network') + prefix = getattr(route, 'prefix') + return network + '/' + str(prefix) + + +class RouteTable(tables.DataTable): + network = tables.Column(get_network_column, + verbose_name=_("Network")) + gateway = tables.Column("gateway", + verbose_name=_("Gateway")) + metric = tables.Column("metric", + verbose_name=_("Metric")) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return ("%(network)s/%(prefix)s via %(gateway)s" % + {'network': datum.network, + 'prefix': datum.prefix, + 'gateway': datum.gateway}) + + class Meta(object): + name = "routes" + verbose_name = _("Route List") + table_actions = (CreateRoute, DeleteRoute) + row_actions = (DeleteRoute,) + + def get_interface(self): + if not hasattr(self, "_interface"): + try: + interface_id = self.kwargs["interface_id"] + self._interface = api.sysinv.host_interface_get( + self.request, interface_id) + except Exception: + redirect = reverse(self.failure_url, + args=(self.kwargs['host_id'], + self.kwargs['interface_id'],)) + msg = _("Unable to retrieve interface details.") + exceptions.handle(self.request, msg, redirect=redirect) + return + return self._interface + + def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs): + super(RouteTable, self).__init__( + request, data=data, needs_form_wrapper=needs_form_wrapper, + **kwargs) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/views.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/views.py new file mode 100755 index 00000000..ba9bb694 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/route/views.py @@ -0,0 +1,51 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import logging + +from django.core.urlresolvers import reverse # noqa + +from horizon import forms +from openstack_dashboard.dashboards.admin.inventory.interfaces.route import \ + forms as route_forms + +LOG = logging.getLogger(__name__) + + +class CreateView(forms.ModalFormView): + form_class = route_forms.CreateRoute + template_name = 'admin/inventory/interfaces/route/create.html' + success_url = 'horizon:admin:inventory:viewinterface' + failure_url = 'horizon:admin:inventory:viewinterface' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'], + self.kwargs['interface_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['host_id'], + self.kwargs['interface_id'],)) + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + context['interface_id'] = self.kwargs['interface_id'] + context['host_id'] = self.kwargs['host_id'] + return context + + def get_initial(self): + return {'interface_id': self.kwargs['interface_id'], + 'host_id': self.kwargs['host_id']} diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/tables.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/tables.py new file mode 100644 index 00000000..b774966e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/tables.py @@ -0,0 +1,211 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.template import defaultfilters as filters +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + +NETWORK_TYPES = ["oam", "infra", "mgmt", "pxeboot"] + + +class DeleteInterface(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Interface", + u"Delete Interfaces", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Interface", + u"Deleted Interfaces", + count + ) + + def allowed(self, request, interface=None): + host = self.table.kwargs['host'] + return (host._administrative == 'locked' and + interface.iftype != 'ethernet') + + def delete(self, request, interface_id): + host_id = self.table.kwargs['host_id'] + try: + api.sysinv.host_interface_delete(request, interface_id) + except Exception: + msg = _('Failed to delete host %(hid)s interface %(iid)s') % { + 'hid': host_id, 'iid': interface_id} + LOG.info(msg) + redirect = reverse('horizon:admin:inventory:detail', + args=(host_id,)) + exceptions.handle(request, msg, redirect=redirect) + + +class CreateInterfaceProfile(tables.LinkAction): + name = "createProfile" + verbose_name = _("Create Interface Profile") + url = "horizon:admin:inventory:addinterfaceprofile" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, datum=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id,)) + + def allowed(self, request, datum): + return not api.sysinv.is_system_mode_simplex(request) + + +class CreateInterface(tables.LinkAction): + name = "create" + verbose_name = _("Create Interface") + url = "horizon:admin:inventory:addinterface" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, datum=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id,)) + + def allowed(self, request, datum): + host = self.table.kwargs['host'] + + if host._administrative != 'locked': + return False + + count = 0 + for i in host.interfaces: + if i.networktype: + count = count + 1 + + if host.subfunctions and 'compute' not in host.subfunctions and \ + count >= len(NETWORK_TYPES): + return False + + return True + + +class EditInterface(tables.LinkAction): + name = "update" + verbose_name = _("Edit Interface") + url = "horizon:admin:inventory:editinterface" + classes = ("ajax-modal", "btn-edit") + + def get_link_url(self, interface=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id, interface.uuid)) + + def allowed(self, request, datum): + host = self.table.kwargs['host'] + return host._administrative == 'locked' + + +def get_attributes(interface): + attr_str = "MTU=%s" % interface.imtu + if interface.iftype == 'ae': + attr_str = "%s, AE_MODE=%s" % (attr_str, interface.aemode) + if interface.aemode in ['balanced', '802.3ad']: + attr_str = "%s, AE_XMIT_HASH_POLICY=%s" % ( + attr_str, interface.txhashpolicy) + if (interface.networktype and + any(network in ['data', 'data-external'] for network in + interface.networktype.split(","))): + attrs = [attr.strip() for attr in attr_str.split(",")] + for a in attrs: + if 'accelerated' in a: + attrs.remove(a) + attr_str = ",".join(attrs) + + if 'False' in interface.dpdksupport: + attr_str = "%s, accelerated=%s" % (attr_str, 'False') + else: + attr_str = "%s, accelerated=%s" % (attr_str, 'True') + return attr_str + + +def get_ports(interface): + port_str_list = ", ".join(interface.portNameList) + return port_str_list + + +def get_port_neighbours(interface): + return interface.portNeighbourList + + +def get_uses(interface): + uses_list = ", ".join(interface.uses) + return uses_list + + +def get_used_by(interface): + used_by_list = ", ".join(interface.used_by) + return used_by_list + + +def get_link_url(interface): + return reverse("horizon:admin:inventory:viewinterface", + args=(interface.host_id, interface.uuid)) + + +class InterfacesTable(tables.DataTable): + ifname = tables.Column('ifname', + verbose_name=_('Name'), + link=get_link_url) + + networktype = tables.Column('networktype', + verbose_name=_('Network Type')) + + iftype = tables.Column('iftype', + verbose_name=_('Type')) + + vlan_id = tables.Column('vlan_id', + verbose_name=_('Vlan ID')) + + ports = tables.Column(get_ports, + verbose_name=_('Port')) + + port_neighbours = tables.Column(get_port_neighbours, + verbose_name=_('Neighbors'), + wrap_list=True, + filters=(filters.unordered_list,)) + + uses = tables.Column(get_uses, + verbose_name=_('Uses')) + + used_by = tables.Column(get_used_by, + verbose_name=_('Used By')) + + providernetworks = tables.Column('providernetworks', + verbose_name=_('Provider Network(s)')) + attributes = tables.Column(get_attributes, + verbose_name=_('Attributes')) + + def __init__(self, *args, **kwargs): + super(InterfacesTable, self).__init__(*args, **kwargs) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.ifname + + class Meta(object): + name = "interfaces" + verbose_name = _("Interfaces") + multi_select = False + table_actions = (CreateInterfaceProfile, CreateInterface,) + row_actions = (EditInterface, DeleteInterface,) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/tabs.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/tabs.py new file mode 100755 index 00000000..30b12d3e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/tabs.py @@ -0,0 +1,41 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import tabs + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = "admin/inventory/interfaces/_detail_overview.html" + + def get_context_data(self, request): + return {} + + +class InterfaceDetailTabs(tabs.TabGroup): + slug = "interface_details" + tabs = (OverviewTab,) diff --git a/cgcs_dashboard/dashboards/admin/inventory/interfaces/views.py b/cgcs_dashboard/dashboards/admin/inventory/interfaces/views.py new file mode 100755 index 00000000..b4aeb6df --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/interfaces/views.py @@ -0,0 +1,397 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon.utils import memoized + +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.interfaces.address import \ + tables as address_tables +from openstack_dashboard.dashboards.admin.inventory.interfaces.forms import \ + AddInterface +from openstack_dashboard.dashboards.admin.inventory.interfaces.forms import \ + AddInterfaceProfile +from openstack_dashboard.dashboards.admin.inventory.interfaces.forms import \ + UpdateInterface +from openstack_dashboard.dashboards.admin.inventory.interfaces.route import \ + tables as route_tables + +LOG = logging.getLogger(__name__) + + +def get_port_data(request, host_id, interface=None): + port_data = [] + show_all_ports = True + + try: + if not interface: + # Create case, host id is not UUID. Need to get the UUID in order + # to retrieve the ports for this host + host = api.sysinv.host_get(request, host_id) + host_id = host.uuid + else: + if not interface.uses: + show_all_ports = False + + port_list = \ + api.sysinv.host_port_list(request, host_id) + + if show_all_ports: + # This is either a create or edit non-default interface + # operation. Get the list of available ports and their + # neighbours + neighbour_list = \ + api.sysinv.host_lldpneighbour_list(request, host_id) + interface_list = api.sysinv.host_interface_list(request, host_id) + + for p in port_list: + port_info = "%s (%s, %s, " % (p.get_port_display_name(), + p.mac, p.pciaddr) + interface_name = '' + for i in interface_list: + if p.interface_uuid == i.uuid: + interface_name = i.ifname + + if interface_name: + port_info += interface_name + ")" + else: + port_info += _("none") + ")" + + if p.bootp: + port_info += " - bootif" + + neighbour_info = [] + for n in neighbour_list: + if p.uuid == n.port_uuid: + if n.port_description: + neighbour = "%s (%s)" % ( + n.port_identifier, n.port_description) + else: + neighbour = "%s" % n.port_identifier + neighbour_info.append(neighbour) + neighbour_info.sort() + port_data_item = port_info, neighbour_info + port_data.append(port_data_item) + else: + # Edit default-interface operation + for p in port_list: + # Since the port->default interface mapping is now strictly + # 1:1, the below condition can only be met at most once for + # the available ports + if p.interface_uuid == interface.uuid: + port_info = "%s (%s, %s, %s)" % ( + p.get_port_display_name(), p.mac, p.pciaddr, + interface.ifname) + + if p.bootp: + port_info += " - bootif" + # Retrieve the neighbours for the port + neighbours = \ + api.sysinv.port_lldpneighbour_list(request, p.uuid) + neighbour_info = [] + if neighbours: + for n in neighbours: + if n.port_description: + neighbour = "%s (%s)" % ( + n.port_identifier, n.port_description) + else: + neighbour = "%s\n" % n.port_identifier + neighbour_info.append(neighbour) + neighbour_info.sort() + port_data_item = port_info, neighbour_info + port_data.append(port_data_item) + except Exception: + redirect = reverse('horizon:admin:inventory:index') + exceptions.handle(request, + _('Unable to retrieve port info details for host ' + '"%s".') % host_id, redirect=redirect) + + return port_data + + +class AddInterfaceView(forms.ModalFormView): + form_class = AddInterface + template_name = 'admin/inventory/interfaces/create.html' + success_url = 'horizon:admin:inventory:detail' + failure_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['host_id'],)) + + def get_context_data(self, **kwargs): + context = super(AddInterfaceView, self).get_context_data(**kwargs) + context['host_id'] = self.kwargs['host_id'] + context['ports'] = get_port_data(self.request, self.kwargs['host_id']) + return context + + def get_initial(self): + initial = super(AddInterfaceView, self).get_initial() + initial['host_id'] = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, initial['host_id']) + except Exception: + exceptions.handle(self.request, _('Unable to retrieve host.')) + initial['ihost_uuid'] = host.uuid + initial['host'] = host + + # get SDN configuration status + try: + sdn_enabled = api.sysinv.get_sdn_enabled(self.request) + sdn_l3_mode = api.sysinv.get_sdn_l3_mode_enabled(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve SDN configuration.')) + initial['sdn_enabled'] = sdn_enabled + initial['sdn_l3_mode_enabled'] = sdn_l3_mode + return initial + + +class AddInterfaceProfileView(forms.ModalFormView): + form_class = AddInterfaceProfile + template_name = 'admin/inventory/interfaces/createprofile.html' + success_url = 'horizon:admin:inventory:detail' + failure_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['host_id'],)) + + def get_myhost_data(self): + if not hasattr(self, "_host"): + host_id = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, host_id) + + all_ports = api.sysinv.host_port_list(self.request, host.uuid) + host.ports = [p for p in all_ports if p.interface_uuid] + for p in host.ports: + p.namedisplay = p.get_port_display_name() + + host.interfaces = api.sysinv.host_interface_list(self.request, + host.uuid) + for i in host.interfaces: + i.ports = [p.get_port_display_name() + for p in all_ports if + p.interface_uuid and p.interface_uuid == i.uuid] + i.ports = ", ".join(i.ports) + i.uses = ", ".join(i.uses) + + except Exception: + redirect = reverse('horizon:admin:inventory:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'host "%s".') % host_id, + redirect=redirect) + self._host = host + return self._host + + def get_context_data(self, **kwargs): + context = super(AddInterfaceProfileView, self).get_context_data( + **kwargs) + context['host_id'] = self.kwargs['host_id'] + context['host'] = self.get_myhost_data() + return context + + def get_initial(self): + initial = super(AddInterfaceProfileView, self).get_initial() + initial['host_id'] = self.kwargs['host_id'] + return initial + + +class UpdateView(forms.ModalFormView): + form_class = UpdateInterface + template_name = 'admin/inventory/interfaces/update.html' + success_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + interface_id = self.kwargs['interface_id'] + host_id = self.kwargs['host_id'] + try: + self._object = api.sysinv.host_interface_get(self.request, + interface_id) + self._object.host_id = host_id + + except Exception: + redirect = reverse("horizon:project:networks:detail", + args=(self.kwargs['host_id'],)) + msg = _('Unable to retrieve interface details') + exceptions.handle(self.request, msg, redirect=redirect) + + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + interface = self._get_object() + context['interface_id'] = interface.uuid + context['host_id'] = interface.host_id + ports = get_port_data(self.request, interface.ihost_uuid, interface) + if ports: + context['ports'] = ports + return context + + def get_initial(self): + interface = self._get_object() + networktype = [] + if interface.networktype: + for network in interface.networktype.split(","): + networktype.append(str(network)) + providernetworks = [] + if interface.providernetworks: + for pn in interface.providernetworks.split(","): + providernetworks.append(str(pn)) + try: + host = api.sysinv.host_get(self.request, interface.host_id) + except Exception: + exceptions.handle(self.request, _('Unable to retrieve host.')) + + # get SDN configuration status + try: + sdn_enabled, sdn_l3_mode = False, False + sdn_enabled = api.sysinv.get_sdn_enabled(self.request) + sdn_l3_mode = api.sysinv.get_sdn_l3_mode_enabled(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve SDN configuration.')) + + return {'id': interface.uuid, + 'host_id': interface.host_id, + 'host': host, + 'ihost_uuid': interface.ihost_uuid, + 'ifname': interface.ifname, + 'iftype': interface.iftype, + 'aemode': interface.aemode, + 'txhashpolicy': interface.txhashpolicy, + # 'ports': interface.ports, + # 'uses': interface.uses, + 'networktype': networktype, + 'providernetworks_data': providernetworks, + 'providernetworks_data-external': providernetworks, + 'providernetworks_pci': providernetworks, + 'providernetworks_sriov': providernetworks, + 'sriov_numvfs': interface.sriov_numvfs, + 'imtu': interface.imtu, + 'ipv4_mode': getattr(interface, 'ipv4_mode', 'disabled'), + 'ipv4_pool': getattr(interface, 'ipv4_pool', None), + 'ipv6_mode': getattr(interface, 'ipv6_mode', 'disabled'), + 'ipv6_pool': getattr(interface, 'ipv6_pool', None), + 'sdn_enabled': sdn_enabled, + 'sdn_l3_mode_enabled': sdn_l3_mode} + + +class DetailView(tables.MultiTableView): + table_classes = (address_tables.AddressTable, + route_tables.RouteTable) + template_name = 'admin/inventory/interfaces/detail.html' + failure_url = reverse_lazy('horizon:admin:inventory:detail') + page_title = "{{ interface.ifname }}" + + def get_addresses_data(self): + try: + interface_id = self.kwargs['interface_id'] + addresses = api.sysinv.address_list_by_interface( + self.request, interface_id=interface_id) + addresses.sort(key=lambda f: (f.address, f.prefix)) + except Exception: + addresses = [] + msg = _('Address list can not be retrieved.') + exceptions.handle(self.request, msg) + return addresses + + def get_routes_data(self): + try: + interface_id = self.kwargs['interface_id'] + routes = api.sysinv.route_list_by_interface( + self.request, interface_id=interface_id) + routes.sort(key=lambda f: (f.network, f.prefix)) + except Exception: + routes = [] + msg = _('Route list can not be retrieved.') + exceptions.handle(self.request, msg) + return routes + + def _get_address_pools(self): + pools = api.sysinv.address_pool_list(self.request) + return {p.uuid: p for p in pools} + + def _add_pool_names(self, interface): + pools = self._get_address_pools() + if getattr(interface, 'ipv4_mode', '') == 'pool': + interface.ipv4_pool_name = pools[interface.ipv4_pool].name + if getattr(interface, 'ipv6_mode', '') == 'pool': + interface.ipv6_pool_name = pools[interface.ipv6_pool].name + return interface + + @memoized.memoized_method + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + interface_id = self.kwargs['interface_id'] + host_id = self.kwargs['host_id'] + try: + self._object = api.sysinv.host_interface_get(self.request, + interface_id) + self._object.host_id = host_id + self._object = self._add_pool_names(self._object) + except Exception: + redirect = reverse("horizon:admin:inventory:detail", + args=(self.kwargs['host_id'],)) + msg = _('Unable to retrieve interface details') + exceptions.handle(self.request, msg, redirect=redirect) + + return self._object + + @memoized.memoized_method + def get_hostname(self, host_uuid): + try: + host = api.sysinv.host_get(self.request, host_uuid) + except Exception: + host = {} + msg = _('Unable to retrieve hostname details.') + exceptions.handle(self.request, msg) + return host.hostname + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + interface = self._get_object() + + hostname = self.get_hostname(interface.host_id) + host_nav = hostname or "Unprovisioned Node" + breadcrumb = [ + (host_nav, reverse('horizon:admin:inventory:detail', + args=(interface.host_id,))), + (_("Interfaces"), None) + ] + context["custom_breadcrumb"] = breadcrumb + + context['interface_id'] = interface.uuid + context['host_id'] = interface.host_id + context['interface'] = interface + return context diff --git a/cgcs_dashboard/dashboards/admin/inventory/lldp/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/lldp/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/lldp/tables.py b/cgcs_dashboard/dashboards/admin/inventory/lldp/tables.py new file mode 100755 index 00000000..d1e89343 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/lldp/tables.py @@ -0,0 +1,46 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.utils.translation import ugettext_lazy as _ +from horizon import tables + +LOG = logging.getLogger(__name__) + + +def get_name(neighbour): + return neighbour.get_local_port_display_name() + + +class LldpNeighboursTable(tables.DataTable): + name = tables.Column(get_name, + verbose_name=_('Name'), + link="horizon:admin:inventory:viewneighbour") + port_identifier = tables.Column('port_identifier', + verbose_name=_('Neighbor')) + port_description = tables.Column('port_description', + verbose_name=_('Port Description')) + ttl = tables.Column('ttl', verbose_name=_('Time To Live (Rx)')) + + system_name = tables.Column('system_name', + verbose_name=_('System Name'), + truncate=100) + dot3_max_frame = tables.Column('dot3_max_frame', + verbose_name=_('Max Frame Size')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.get_local_port_display_name() + + class Meta(object): + name = "neighbours" + verbose_name = _("LLDP Neighbors") + multi_select = False diff --git a/cgcs_dashboard/dashboards/admin/inventory/lldp/views.py b/cgcs_dashboard/dashboards/admin/inventory/lldp/views.py new file mode 100755 index 00000000..7c4fb6b2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/lldp/views.py @@ -0,0 +1,101 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon.utils import memoized +from horizon import views +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class DetailNeighbourView(views.HorizonTemplateView): + template_name = 'admin/inventory/_detail_neighbour.html' + page_title = "{{ localportname }}" + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + neighbour_uuid = self.kwargs['neighbour_uuid'] + try: + self._object = \ + api.sysinv.host_lldpneighbour_get(self.request, + neighbour_uuid) + + except Exception: + redirect = reverse('horizon:admin:inventory:index') + msg = _('Unable to retrieve LLDP neighbor details "%s".') \ + % neighbour_uuid + exceptions.handle(self.request, msg, redirect=redirect) + + return self._object + + def get_context_data(self, **kwargs): + context = super(DetailNeighbourView, self).get_context_data(**kwargs) + # Context "neighbour" is referenced in _detail_neighbour.html + # Reformat some attributes for better display + neighbour = self._get_object() + context['localportname'] = neighbour.get_local_port_display_name() + if neighbour.system_capabilities: + context['systemcaps'] = \ + neighbour.system_capabilities.replace(',', '\n') + if neighbour.dot1_proto_vids: + context['dot1provids'] = \ + neighbour.dot1_proto_vids.replace(',', '\n') + if neighbour.dot1_vlan_names: + context['vlannames'] = neighbour.dot1_vlan_names.replace(',', '\n') + if neighbour.dot1_proto_ids: + context['dot1protoids'] = \ + neighbour.dot1_proto_ids.replace(',', '\n') + if neighbour.dot1_lag: + context['dot1lag'] = neighbour.dot1_lag.replace(',', '\n') + if neighbour.dot3_mac_status: + # The dot3_mac_status has irregular format. An example is + # auto-negotiation-capable=y,auto-negotiation-enabled=y,capability + # =10-base-t-fd,100-base-t4,1000-base-t-fd,mau-type=4-pair- + # category-5-utp-fd + status_list = neighbour.dot3_mac_status.split(',') + if status_list: + mac_status = [] + for i in status_list: + if "=" in i: + mac_status.append(i) + else: + mac_status[-1] += "," + i + context['dot3macstatus'] = "\n".join(mac_status) + if neighbour.dot3_power_mdi: + context['dot3powermdi'] = \ + neighbour.dot3_power_mdi.replace(',', '\n') + context['neighbour'] = neighbour + + hostname = self.get_hostname(neighbour.host_uuid) + host_nav = hostname or "Unprovisioned Node" + breadcrumb = [ + (host_nav, reverse('horizon:admin:inventory:detail', + args=(neighbour.host_uuid,))), + (_("Neighbors"), None) + ] + context["custom_breadcrumb"] = breadcrumb + + return context + + @memoized.memoized_method + def get_hostname(self, host_uuid): + try: + host = api.sysinv.host_get(self.request, host_uuid) + except Exception: + host = {} + msg = _('Unable to retrieve hostname details.') + exceptions.handle(self.request, msg) + return host.hostname diff --git a/cgcs_dashboard/dashboards/admin/inventory/memorys/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/memorys/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/memorys/forms.py b/cgcs_dashboard/dashboards/admin/inventory/memorys/forms.py new file mode 100755 index 00000000..d1cb81d7 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/memorys/forms.py @@ -0,0 +1,363 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cgtsclient import exc +from django.core.urlresolvers import reverse # noqa +from django import shortcuts +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UpdateMemory(forms.SelfHandlingForm): + host = forms.CharField(label=_("host"), + required=False, + widget=forms.widgets.HiddenInput) + + host_id = forms.CharField(label=_("host_id"), + required=False, + widget=forms.widgets.HiddenInput) + + platform_memory = forms.CharField( + label=_("Platform Memory for Node 0"), + required=False) + + vm_hugepages_nr_2M = forms.CharField( + label=_("# of VM 2M Hugepages Node 0"), + required=False) + + vm_hugepages_nr_1G = forms.CharField( + label=_("# of VM 1G Hugepages Node 0"), + required=False) + + platform_memory_two = forms.CharField( + label=_("Platform Memory for Node 1"), + required=False) + + vm_hugepages_nr_2M_two = forms.CharField( + label=_("# of VM 2M Hugepages Node 1"), + required=False) + + vm_hugepages_nr_1G_two = forms.CharField( + label=_("# of VM 1G Hugepages Node 1"), + required=False) + + platform_memory_three = forms.CharField( + label=_("Platform Memory for Node 2"), + required=False) + + vm_hugepages_nr_2M_three = forms.CharField( + label=_("# of VM 2M Hugepages Node 2"), + required=False) + + vm_hugepages_nr_1G_three = forms.CharField( + label=_("# of VM 1G Hugepages Node 2"), + required=False) + + platform_memory_four = forms.CharField( + label=_("Platform Memory for Node 3"), + required=False) + + vm_hugepages_nr_2M_four = forms.CharField( + label=_("# of VM 2M Hugepages Node 3"), + required=False) + + vm_hugepages_nr_1G_four = forms.CharField( + label=_("# of VM 1G Hugepages Node 3"), + required=False) + + failure_url = 'horizon:admin:inventory:detail' + + def __init__(self, request, *args, **kwargs): + super(UpdateMemory, self).__init__(request, *args, **kwargs) + + self.host = kwargs['initial']['host'] + + memory_fieldsets = [ + { + 'platform_memory': self.fields['platform_memory'], + 'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M'], + 'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G'] + }, + { + 'platform_memory': self.fields['platform_memory_two'], + 'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M_two'], + 'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G_two'] + }, + { + 'platform_memory': self.fields['platform_memory_three'], + 'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M_three'], + 'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G_three'] + }, + { + 'platform_memory': self.fields['platform_memory_four'], + 'vm_hugepages_nr_2M': self.fields['vm_hugepages_nr_2M_four'], + 'vm_hugepages_nr_1G': self.fields['vm_hugepages_nr_1G_four'] + } + ] + + count = 0 + for m in self.host.memorys: + count = count + 1 + for n in self.host.nodes: + if m.inode_uuid == n.uuid: + field_set = memory_fieldsets[int(n.numa_node)] + platform_field = field_set['platform_memory'] + platform_field.help_text = \ + 'Minimum platform memory(MiB): ' + \ + str(m.minimum_platform_reserved_mib) + + platform_field.initial = str(m.platform_reserved_mib) + + vm_2M_field = field_set['vm_hugepages_nr_2M'] + vm_2M_field.help_text = \ + 'Maximum 2M pages: ' + \ + str(m.vm_hugepages_possible_2M) + + if m.vm_hugepages_nr_2M_pending: + vm_2M_field.initial = str(m.vm_hugepages_nr_2M_pending) + elif m.vm_hugepages_nr_2M: + vm_2M_field.initial = str(m.vm_hugepages_nr_2M) + else: + vm_2M_field.initial = '0' + + vm_1G_field = field_set['vm_hugepages_nr_1G'] + vm_1g_supported = m.vm_hugepages_use_1G != 'False' + if vm_1g_supported: + help_msg = 'Maximum 1G pages: ' + \ + str(m.vm_hugepages_possible_1G) + else: + help_msg = 'This node does not support 1G hugepages' + + vm_1G_field.help_text = help_msg + + if m.vm_hugepages_nr_1G_pending: + vm_1G_field.initial = str(m.vm_hugepages_nr_1G_pending) + elif m.vm_hugepages_nr_1G: + vm_1G_field.initial = str(m.vm_hugepages_nr_1G) + elif vm_1g_supported: + vm_1G_field.initial = '0' + else: + vm_1G_field.initial = '' + + if not vm_1g_supported: + vm_1G_field.widget.attrs['disabled'] = 'disabled' + + break + + while count < 4: + field_set = memory_fieldsets[count] + field_set['platform_memory'].widget = \ + forms.widgets.HiddenInput() + field_set['vm_hugepages_nr_2M'].widget = \ + forms.widgets.HiddenInput() + field_set['vm_hugepages_nr_1G'].widget = \ + forms.widgets.HiddenInput() + count += 1 + + def clean(self): + cleaned_data = super(UpdateMemory, self).clean() + # host_id = cleaned_data.get('host_id') + return cleaned_data + + def handle(self, request, data): + + host_id = data['host_id'] + del data['host_id'] + del data['host'] + + node = [] + node.append('node0') + + if data['platform_memory_two'] or \ + data['vm_hugepages_nr_2M_two'] or \ + data['vm_hugepages_nr_1G_two']: + node.append('node1') + + if data['platform_memory_three'] or \ + data['vm_hugepages_nr_2M_three'] or \ + data['vm_hugepages_nr_1G_three']: + node.append('node2') + + if data['platform_memory_four'] or \ + data['vm_hugepages_nr_2M_four'] or \ + data['vm_hugepages_nr_1G_four']: + node.append('node3') + + # host = api.sysinv.host_get(request, host_id) + pages_1G = {} + pages_2M = {} + plat_mem = {} + + # Node 0 arguments + if not data['platform_memory']: + del data['platform_memory'] + else: + plat_mem['node0'] = data['platform_memory'] + + if not data['vm_hugepages_nr_2M']: + del data['vm_hugepages_nr_2M'] + else: + pages_2M['node0'] = data['vm_hugepages_nr_2M'] + + if not data['vm_hugepages_nr_1G']: + del data['vm_hugepages_nr_1G'] + else: + pages_1G['node0'] = data['vm_hugepages_nr_1G'] + + # Node 1 arguments + if not data['platform_memory_two']: + del data['platform_memory_two'] + else: + plat_mem['node1'] = data['platform_memory_two'] + + if not data['vm_hugepages_nr_2M_two']: + del data['vm_hugepages_nr_2M_two'] + else: + pages_2M['node1'] = data['vm_hugepages_nr_2M_two'] + + if not data['vm_hugepages_nr_1G_two']: + del data['vm_hugepages_nr_1G_two'] + else: + pages_1G['node1'] = data['vm_hugepages_nr_1G_two'] + + # Node 2 arguments + if not data['platform_memory_three']: + del data['platform_memory_three'] + else: + plat_mem['node2'] = data['platform_memory_three'] + + if not data['vm_hugepages_nr_2M_three']: + del data['vm_hugepages_nr_2M_three'] + else: + pages_2M['node2'] = data['vm_hugepages_nr_2M_three'] + + if not data['vm_hugepages_nr_1G_three']: + del data['vm_hugepages_nr_1G_three'] + else: + pages_1G['node2'] = data['vm_hugepages_nr_1G_three'] + + # Node 3 arguments + if not data['platform_memory_four']: + del data['platform_memory_four'] + else: + plat_mem['node3'] = data['platform_memory_four'] + + if not data['vm_hugepages_nr_2M_four']: + del data['vm_hugepages_nr_2M_four'] + else: + pages_2M['node3'] = data['vm_hugepages_nr_2M_four'] + + if not data['vm_hugepages_nr_1G_four']: + del data['vm_hugepages_nr_1G_four'] + else: + pages_1G['node3'] = data['vm_hugepages_nr_1G_four'] + + try: + for nd in node: + node_found = False + for m in self.host.memorys: + for n in self.host.nodes: + if m.inode_uuid == n.uuid: + if int(n.numa_node) == int(node.index(nd)): + node_found = True + break + if node_found: + break + + if node_found: + new_data = {} + if nd in plat_mem: + new_data['platform_reserved_mib'] = plat_mem[nd] + if nd in pages_2M: + new_data['vm_hugepages_nr_2M_pending'] = pages_2M[nd] + if nd in pages_1G: + new_data['vm_hugepages_nr_1G_pending'] = pages_1G[nd] + + if new_data: + api.sysinv.host_memory_update(request, m.uuid, + **new_data) + + else: + msg = _('Failed to find %s') % nd + messages.error(request, msg) + LOG.error(msg) + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[host_id]) + return shortcuts.redirect(redirect) + + msg = _('Memory allocation has been successfully ' + 'updated.') + LOG.debug(msg) + messages.success(request, msg) + return self.host.memorys + except exc.ClientException as ce: + # Allow REST API error message to appear on UI + messages.error(request, ce) + LOG.error(ce) + + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[host_id]) + return shortcuts.redirect(redirect) + + except Exception: + msg = _('Failed to update memory allocation') + LOG.info(msg) + redirect = reverse(self.failure_url, args=[host_id]) + exceptions.handle(request, msg, redirect=redirect) + + +class AddMemoryProfile(forms.SelfHandlingForm): + host_id = forms.CharField(widget=forms.widgets.HiddenInput) + profilename = forms.CharField(label=_("Memory Profile Name"), + required=True) + + failure_url = 'horizon:admin:inventory:detail' + + def __init__(self, *args, **kwargs): + super(AddMemoryProfile, self).__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super(AddMemoryProfile, self).clean() + # host_id = cleaned_data.get('host_id') + return cleaned_data + + def handle(self, request, data): + + memoryProfileName = data['profilename'] + try: + memoryProfile = api.sysinv.host_memprofile_create(request, **data) + msg = _('Memory Profile "%s" was successfully created.') % \ + memoryProfileName + LOG.debug(msg) + messages.success(request, msg) + return memoryProfile + except exc.ClientException as ce: + # Display REST API error message on UI + messages.error(request, ce) + LOG.error(ce) + + # Redirect to failure pg + redirect = reverse(self.failure_url, args=[data['host_id']]) + return shortcuts.redirect(redirect) + except Exception: + msg = _('Failed to create memory profile "%s".') % \ + memoryProfileName + LOG.info(msg) + redirect = reverse(self.failure_url, + args=[data['host_id']]) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/memorys/tables.py b/cgcs_dashboard/dashboards/admin/inventory/memorys/tables.py new file mode 100644 index 00000000..02035a1e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/memorys/tables.py @@ -0,0 +1,99 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse # noqa +from django import template +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UpdateMemory(tables.LinkAction): + name = "updatememory" + verbose_name = _("Update Memory") + url = "horizon:admin:inventory:updatememory" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, memory=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id,)) + + def allowed(self, request, memory=None): + host = self.table.kwargs['host'] + return (host._administrative == 'locked' and + host.subfunctions and + 'compute' in host.subfunctions) + + +class CreateMemoryProfile(tables.LinkAction): + name = "createMemoryProfile" + verbose_name = _("Create Memory Profile") + url = "horizon:admin:inventory:addmemoryprofile" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, datum=None): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id,)) + + def allowed(self, request, datum): + host = self.table.kwargs['host'] + if host.subfunctions and 'compute' not in host.subfunctions: + return False + return (host.invprovision == 'provisioned' and + not api.sysinv.is_system_mode_simplex(request)) + + +def get_processor_memory(memory): + if memory.hugepages_configured == 'True': + template_name = \ + 'admin/inventory/memorys/_memoryfunction_hugepages.html' + else: + template_name = \ + 'admin/inventory/memorys/_memoryfunction_hugepages_other.html' + context = {"memory": memory} + return template.loader.render_to_string(template_name, context) + + +def get_vswitch_hugepages(memory): + template_name = 'admin/inventory/memorys/_vswitchfunction_hugepages.html' + context = {"memory": memory} + return template.loader.render_to_string(template_name, context) + + +def get_vm_hugepages(memory): + template_name = 'admin/inventory/memorys/_vmfunction_hugepages.html' + context = {"memory": memory} + return template.loader.render_to_string(template_name, context) + + +class MemorysTable(tables.DataTable): + processor = tables.Column('numa_node', + verbose_name=_('Processor')) + + memory = tables.Column(get_processor_memory, + verbose_name=_('Memory')) + + vswitch_huge = tables.Column(get_vswitch_hugepages, + verbose_name=_('VSwitch Huge Pages')) + + vm_huge = tables.Column(get_vm_hugepages, + verbose_name=_('VM Pages')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + class Meta(object): + name = "memorys" + verbose_name = _("Memory") + multi_select = False + table_actions = (UpdateMemory, CreateMemoryProfile,) diff --git a/cgcs_dashboard/dashboards/admin/inventory/memorys/views.py b/cgcs_dashboard/dashboards/admin/inventory/memorys/views.py new file mode 100755 index 00000000..370769a0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/memorys/views.py @@ -0,0 +1,117 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.memorys.forms import \ + AddMemoryProfile +from openstack_dashboard.dashboards.admin.inventory.memorys.forms import \ + UpdateMemory + +LOG = logging.getLogger(__name__) + + +class UpdateMemoryView(forms.ModalFormView): + form_class = UpdateMemory + template_name = 'admin/inventory/memorys/edit_hp_memory.html' + success_url = 'horizon:admin:inventory:detail' + context_object_name = "memorys" + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + host_id = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, host_id) + host.memorys = api.sysinv.host_memory_list(self.request, + host.uuid) + host.nodes = \ + api.sysinv.host_node_list(self.request, host.uuid) + self._object = host + self._object.host_id = host_id + except Exception as e: + LOG.exception(e) + redirect = reverse("horizon:project:networks:detail", + args=(host_id)) + msg = _('Unable to retrieve memory details') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateMemoryView, self).get_context_data( + **kwargs) + host = self._get_object() + context['host_id'] = host.host_id + return context + + def get_initial(self): + host = self._get_object() + + return {'host': host, + 'host_id': host.host_id, } + + +class AddMemoryProfileView(forms.ModalFormView): + form_class = AddMemoryProfile + template_name = 'admin/inventory/memorys/createprofile.html' + success_url = 'horizon:admin:inventory:detail' + failure_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['host_id'],)) + + def get_myhost_data(self): + if not hasattr(self, "_host"): + host_id = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, host_id) + host.nodes = api.sysinv.host_node_list(self.request, host.uuid) + host.memory = \ + api.sysinv.host_memory_list(self.request, host.uuid) + + numa_node_tuple_list = [] + for m in host.memory: + node = api.sysinv.host_node_get(self.request, m.inode_uuid) + numa_node_tuple_list.append((node.numa_node, m)) + + host.numa_nodes = numa_node_tuple_list + except Exception: + redirect = reverse('horizon:admin:inventory:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'host "%s".') % host_id, + redirect=redirect) + self._host = host + return self._host + + def get_context_data(self, **kwargs): + context = super(AddMemoryProfileView, self).get_context_data(**kwargs) + context['host_id'] = self.kwargs['host_id'] + context['host'] = self.get_myhost_data() + return context + + def get_initial(self): + initial = super(AddMemoryProfileView, self).get_initial() + initial['host_id'] = self.kwargs['host_id'] + return initial diff --git a/cgcs_dashboard/dashboards/admin/inventory/panel.py b/cgcs_dashboard/dashboards/admin/inventory/panel.py new file mode 100755 index 00000000..dd3dc371 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/panel.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from django.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class Inventory(horizon.Panel): + name = _("Host Inventory") + slug = 'inventory' + # permissions = ('openstack.roles.admin',) + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(Inventory, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + + +dashboard.Admin.register(Inventory) diff --git a/cgcs_dashboard/dashboards/admin/inventory/ports/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/ports/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/ports/forms.py b/cgcs_dashboard/dashboards/admin/inventory/ports/forms.py new file mode 100755 index 00000000..70284230 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/ports/forms.py @@ -0,0 +1,123 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UpdatePort(forms.SelfHandlingForm): + SPEED_CHOICES = ( + ('10', _(" 10baseT")), + ('100', _(" 100baseT")), + ('1000', _(" 1000baseT")), + ('10000', _("10000baseT")), + ) + + AUTO_NEG_CHOICES = ( + ('yes', _("yes")), + ('no', _("no")), + ('na', _("na")), + ) + + host_uuid = forms.CharField(label=_("host_uuid"), + required=False, + widget=forms.widgets.HiddenInput) + host_id = forms.CharField(label=_("host_id"), + required=False, + widget=forms.widgets.HiddenInput) + id = forms.CharField(label=_("id"), + required=False, + widget=forms.widgets.HiddenInput) + + name = forms.CharField(label=_("Name"), + required=False, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + newname = forms.CharField(label=_("Name"), + required=False) + oldname = forms.CharField(label=_("Name"), + required=False, + widget=forms.widgets.HiddenInput) + + autoneg = forms.ChoiceField(label=_("Hidden Auto Neg"), + required=False, + choices=AUTO_NEG_CHOICES, + widget=forms.widgets.HiddenInput) + autonegbool = forms.BooleanField(label=_("Auto Negotiation"), + required=False) + + # Configurable Speed will be added later + # + # speed = forms.ChoiceField(label=_("Speed"), + # initial='speed', + # choices=SPEED_CHOICES, + # required=False) + + failure_url = 'horizon:admin:inventory:detail' + + def __init__(self, *args, **kwargs): + super(UpdatePort, self).__init__(*args, **kwargs) + + name = kwargs['initial']['name'] + if name: + self.fields['newname'].widget = forms.widgets.HiddenInput() + else: + self.fields['name'].widget = forms.widgets.HiddenInput() + + autoneg = kwargs['initial']['autoneg'] + if autoneg.lower() == 'na': + self.fields['autonegbool'].widget = forms.widgets.HiddenInput() + + def clean(self): + cleaned_data = super(UpdatePort, self).clean() + return cleaned_data + + def handle(self, request, data): + deviceName = data['newname'] + if data['name']: + deviceName = data['name'] + elif data['newname'] != data['oldname']: + data['namedisplay'] = data['newname'] + del data['newname'] + del data['oldname'] + + if data['autoneg'] != 'na': + if data['autonegbool']: + data['autoneg'] = 'Yes' + else: + data['autoneg'] = 'No' + del data['autonegbool'] + + host_id = data['host_id'] + del data['host_id'] + + port_id = data['id'] + del data['id'] + + try: + port = api.sysinv.host_port_update(request, port_id, **data) + msg = _('Port "%s" was successfully updated.') % deviceName + LOG.debug(msg) + messages.success(request, msg) + return port + except Exception: + msg = _('Failed to update port "%s".') % deviceName + LOG.info(msg) + redirect = reverse(self.failure_url, args=[host_id]) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/inventory/ports/tables.py b/cgcs_dashboard/dashboards/admin/inventory/ports/tables.py new file mode 100755 index 00000000..99a72af8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/ports/tables.py @@ -0,0 +1,89 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse # noqa +from django import template +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables + +LOG = logging.getLogger(__name__) + + +class UpdatePort(tables.LinkAction): + name = "update" + verbose_name = _("Edit Port") + url = "horizon:admin:inventory:editport" + classes = ("ajax-modal", "btn-edit") + + def get_link_url(self, port): + host_id = self.table.kwargs['host_id'] + return reverse(self.url, args=(host_id, port.uuid)) + + def allowed(self, request, port=None): + host = self.table.kwargs['host'] + return host._administrative == 'locked' + + +def get_devicetype(port): + template_name = 'admin/inventory/ports/_ports_devicetype.html' + context = {"port": port} + return template.loader.render_to_string(template_name, context) + + +def get_name(port): + return port.get_port_display_name() + + +def get_bootp(port): + if port.bootp: + return port.bootp + else: + return False + + +def get_link_url(port): + return reverse("horizon:admin:inventory:viewport", + args=(port.host_uuid, port.uuid)) + + +class PortsTable(tables.DataTable): + name = tables.Column(get_name, + verbose_name=_('Name'), + link=get_link_url) + + mac = tables.Column('mac', + verbose_name=_('MAC Address')) + pciaddr = tables.Column('pciaddr', + verbose_name=_('PCI Address')) + numa_node = tables.Column('numa_node', + verbose_name=_('Processor')) + autoneg = tables.Column('autoneg', + verbose_name=_('Auto Negotiation')) + # speed = tables.Column('speed', + # verbose_name=_('Speed (Mbps)')) + bootp = tables.Column(get_bootp, + verbose_name=_('Boot Interface')) + dpdksupport = tables.Column('dpdksupport', + verbose_name=_('Accelerated')) + devicetype = tables.Column(get_devicetype, + verbose_name=_('Device Type')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.get_port_display_name() + + class Meta(object): + name = "ports" + verbose_name = _("Ports") + multi_select = False + # row_actions = (UpdatePort,) diff --git a/cgcs_dashboard/dashboards/admin/inventory/ports/tabs.py b/cgcs_dashboard/dashboards/admin/inventory/ports/tabs.py new file mode 100755 index 00000000..8b0e4654 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/ports/tabs.py @@ -0,0 +1,41 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import tabs + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = "admin/inventory/ports/_detail_overview.html" + + def get_context_data(self, request): + return {} + + +class PortDetailTabs(tabs.TabGroup): + slug = "port_details" + tabs = (OverviewTab,) diff --git a/cgcs_dashboard/dashboards/admin/inventory/ports/views.py b/cgcs_dashboard/dashboards/admin/inventory/ports/views.py new file mode 100755 index 00000000..76941a63 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/ports/views.py @@ -0,0 +1,128 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon.utils import memoized +from horizon import views +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.ports.forms import \ + UpdatePort + +LOG = logging.getLogger(__name__) + + +class UpdateView(forms.ModalFormView): + form_class = UpdatePort + template_name = 'admin/inventory/ports/update.html' + context_object_name = 'port' + success_url = 'horizon:admin:inventory:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['host_id'],)) + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + port_id = self.kwargs['port_id'] + host_id = self.kwargs['host_id'] + try: + self._object = api.sysinv.host_port_get(self.request, port_id) + self._object.host_id = host_id + except Exception: + redirect = reverse("horizon:project:networks:detail", + args=(host_id)) + msg = _('Unable to retrieve port details') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + port = self._get_object() + context['port_id'] = port.uuid + context['host_id'] = port.host_id + return context + + def get_initial(self): + port = self._get_object() + name = port.get_port_display_name() + if port.autoneg: + autonegbool = (port.autoneg.lower() == 'yes') + autoneg = port.autoneg.lower() + else: + autonegbool = False + autoneg = 'na' + return {'host_uuid': port.host_uuid, + 'host_id': port.host_id, + 'id': port.uuid, + 'name': port.name, + 'newname': name, + 'oldname': name, + # 'speed': port.speed, # to be added in future + 'autoneg': autoneg, + 'autonegbool': autonegbool} + + +class DetailView(views.HorizonTemplateView): + template_name = 'admin/inventory/ports/detail.html' + page_title = '{% if port.name %} {{ port.name }}' \ + '{% else %}{{ port.namedisplay }}' \ + '{% endif %}' + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + port_id = self.kwargs['port_id'] + host_id = self.kwargs['host_id'] + try: + self._object = api.sysinv.host_port_get(self.request, + port_id) + self._object.host_id = host_id + + except Exception: + redirect = reverse("horizon:admin:inventory:detail", + args=(self.kwargs['host_id'],)) + msg = _('Unable to retrieve port details') + exceptions.handle(self.request, msg, redirect=redirect) + + return self._object + + @memoized.memoized_method + def get_hostname(self, host_uuid): + try: + host = api.sysinv.host_get(self.request, host_uuid) + except Exception: + host = {} + msg = _('Unable to retrieve hostname details.') + exceptions.handle(self.request, msg) + return host.hostname + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + port = self._get_object() + + hostname = self.get_hostname(port.host_id) + host_nav = hostname or "Unprovisioned Node" + breadcrumb = [ + (host_nav, reverse('horizon:admin:inventory:detail', + args=(port.host_id,))), + (_("Ports"), None) + ] + context["custom_breadcrumb"] = breadcrumb + + context['port_id'] = port.uuid + context['host_id'] = port.host_id + context['port'] = port + return context diff --git a/cgcs_dashboard/dashboards/admin/inventory/sensors/__init__.py b/cgcs_dashboard/dashboards/admin/inventory/sensors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/inventory/sensors/forms.py b/cgcs_dashboard/dashboards/admin/inventory/sensors/forms.py new file mode 100644 index 00000000..f6782b08 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/sensors/forms.py @@ -0,0 +1,235 @@ +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cgtsclient import exc +from django.core.urlresolvers import reverse # noqa +from django import shortcuts +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class AddSensorGroup(forms.SelfHandlingForm): + + DATA_TYPE_CHOICES = ( + (None, _(" +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_cpufunctions.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_cpufunctions.html new file mode 100755 index 00000000..a670d638 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_cpufunctions.html @@ -0,0 +1,25 @@ +{% load horizon i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {% if host.cpus %} +
+
{% trans "Processor Model: " %}
+
{{ host.cpu_model }}
+
{% trans "Processors: " %}
+
{{ host.nodes|length }}
+
{% trans "Physical Cores Per Processor: " %}
+
{{ host.physical_cores|get_value:0 }}
+
{% trans "Hyper-Threading: " %}
+
{{ host.hyperthreading }}
+
+
+ {{ cpufunctions_table.render }} +
+ {% else %} +
{% trans "No CPU topology information available" %}
+ {% endif %} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_devices.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_devices.html new file mode 100755 index 00000000..82a72d27 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_devices.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ devices_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_interfaces.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_interfaces.html new file mode 100755 index 00000000..3b9b02d7 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_interfaces.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ interfaces_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_lldp.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_lldp.html new file mode 100755 index 00000000..553641f2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_lldp.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ neighbours_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group.html new file mode 100755 index 00000000..d11555f3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Local Volume Group Details" %}{% endblock %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group_overview.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group_overview.html new file mode 100755 index 00000000..cf7eca9e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_local_volume_group_overview.html @@ -0,0 +1,42 @@ +{% load i18n sizeformat %} + +

{{ lvg.lvm_vg_name }}

+ +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "UUID" %}
+
{{ lvg.uuid }}
+
{% trans "State" %}
+
{{ lvg.vg_state }}
+
{% trans "Access" %}
+
{{ lvg.lvm_vg_access }}
+
+
+
{% trans "Size" %}
+
{{ lvg.lvm_vg_size | filesizeformat }}
+
{% trans "Max Local Volumes" %}
+
{{ lvg.lvm_max_lv }}
+
{% trans "Current Local Volumes" %}
+
{{ lvg.lvm_cur_lv }}
+
{% trans "Max Physical Volumes" %}
+
{{ lvg.lvm_max_pv }}
+
{% trans "Current Physical Volumes" %}
+
{{ lvg.lvm_cur_pv }}
+
{% trans "Volume Group Total Physical Extents" %}
+
{{ lvg.lvm_vg_total_pe }}
+
{% trans "Volume Group Free Physical Extents" %}
+
{{ lvg.lvm_vg_free_pe }}
+
+
+
{% trans "Created At" %}
+
{{ lvg.created_at }}
+
{% trans "Updated At" %}
+
{{ lvg.updated_at }}
+
+
+
+
diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_memorys.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_memorys.html new file mode 100755 index 00000000..6f8b9785 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_memorys.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ memorys_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_neighbour.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_neighbour.html new file mode 100755 index 00000000..2a9c8b89 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_neighbour.html @@ -0,0 +1,94 @@ +{% extends 'base.html' %} +{% load i18n sizeformat breadcrumb_nav %} +{% block title %}{% trans "LLDP Neighbor Detail"%}{% endblock %} + +{% block main %} +

{% trans "LLDP Neighbor Overview" %}

+
+
+
{% trans "Local Port Name" %}
+
{{ localportname }}
+
+
+
{% trans "Neighbor Identifier" %}
+
{{ neighbour.port_identifier }}
+ {% if neighbour.chassis_id %} +
{% trans "Chassis" %}
+
{{ neighbour.chassis_id }}
+ {% endif %} + {% if neighbour.port_description %} +
{% trans "Description" %}
+
{{ neighbour.port_description }}
+ {% endif %} + {% if neighbour.ttl %} +
{% trans "Time To Live (Rx):" %}
+
{{ neighbour.ttl }}
+ {% endif %} + {% if neighbour.msap %} +
{% trans "MAC Service Access Point" %}
+
{{ neighbour.msap }}
+ {% endif %} + {% if neighbour.system_name %} +
{% trans "System Name" %}
+
{{ neighbour.system_name }}
+ {% endif %} + {% if neighbour.system_description %} +
{% trans "System Description" %}
+
{{ neighbour.system_description }}
+ {% endif %} + {% if systemcaps %} +
{% trans "System Capabilities" %}
+
{{ systemcaps|linebreaksbr }}
+ {% endif %} + {% if neighbour.management_address %} +
{% trans "Management Address" %}
+
{{ neighbour.management_address }}
+ {% endif %} +
+
+ {% if dot1lag %} +
{% trans "Dot1 Link Aggregation" %}
+
{{ dot1lag|linebreaksbr }}
+ {% endif %} + {% if neighbour.dot_port_vid %} +
{% trans "Dot1 Port VID" %}
+
{{ neighbour.dot1_port_vid }}
+ {% endif %} + {% if dot1provids %} +
{% trans "Dot1 Protocol VIDs" %}
+
{{ dot1provids|linebreaksbr }}
+ {% endif %} + {% if vlannames %} +
{% trans "Dot1 VLANs" %}
+
{{ vlannames|linebreaksbr }}
+ {% endif %} + {% if dot1protoids %} +
{% trans "Dot1 Protocol IDs" %}
+
{{ dot1protoids|linebreaksbr }}
+ {% endif %} + {% if neighbour.dot1_vid_digest %} +
{% trans "Dot1 VID Digest" %}
+
{{ neighbour.dot1_vid_digest }}
+ {% endif %} + {% if neighbour.dot1_management_vid %} +
{% trans "Dot1 Management VID" %}
+
{{ neighbour.dot1_management_vid }}
+ {% endif %} + +
+
+ {% if dot3macstatus %} +
{% trans "Dot3 MAC Status" %}
+
{{ dot3macstatus|linebreaksbr }}
+ {% endif %} + {% if dot3powermdi %} +
{% trans "Dot3 Power MDI" %}
+
{{ dot3powermdi|linebreaksbr }}
+ {% endif %} + {% if neighbour.dot3_max_frame %} +
{% trans "Dot3 Max Frame Size" %}
+
{{ neighbour.dot3_max_frame }}
+ {% endif %} +
+
+{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_overview.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_overview.html new file mode 100755 index 00000000..8c9065ba --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_overview.html @@ -0,0 +1,95 @@ +{% load i18n sizeformat %} + +
+

{% trans "Host Info" %}

+
+
+
{% trans "Host Name" %}
+
{{ host.hostname }}
+
{% trans "Personality" %}
+
{{ host.personality }}
+ {% if host.personality and host.additional_subfunctions %} +
{% trans "Subfunctions" %}
+
{{ host.subfunctions|join:", " |title }}
+ {% endif %} + {% if host.pers_subtype and host.personality != 'Compute' %} +
{% trans "Personality Sub-Type" %}
+
{{ host.pers_subtype }}
+ {% endif %} + {% if host.peers %} +
{% trans "Replication Group" %}
+
{{ host.peers }}
+ {% endif %} +
{% trans "Host UUID" %}
+
{{ host.uuid }}
+
{% trans "Host ID" %}
+
{{ host.id }}
+
+
+
{% trans "Management MAC" %}
+
{{ host.mgmt_mac }}
+
{% trans "Management IP" %}
+
{{ host.mgmt_ip }}
+
{% trans "Serial ID" %}
+
{{ host.serialid }}
+
{% trans "Location" %}
+
{{ host.location|default:_("Not Specified") }}
+
{% trans "Serial Line Carrier Detect" %}
+
{{ host.ttys_dcd }}
+
{% trans "Created TimeStamp" %}
+
{{ host.created_at|parse_isotime }}
+
{% trans "Updated TimeStamp" %}
+
{{ host.updated_at|parse_isotime }}
+
+
+
{% trans "Administrative State" %}
+
{{ host.administrative }}
+
{% trans "Operational State" %}
+
{{ host.operational }}
+
{% trans "Availability State" %}
+
{{ host.availability }}
+ {% if host.personality and host.is_cpe %} +
{% trans "Subfunction Operational State" %}
+
{{ host.subfunction_oper }}
+
{% trans "Subfunction Availability State" %}
+
{{ host.subfunction_avail }}
+ {% endif %} +
+
+
{% trans "Patch Current" %}
+
{{ host.patch_current }}
+
{% trans "Reboot Required" %}
+
{{ host.requires_reboot}}
+
{% trans "Patch Agent State" %}
+
{{ host.patch_state }}
+
+
+ +
+

{% trans "Installation Parameters" %}

+
+
+
{% trans "Boot Device" %}
+
{{ host.boot_device }}
+
{% trans "Rootfs Device" %}
+
{{ host.rootfs_device }}
+
{% trans "Installation Output" %}
+
{{ host.install_output }}
+
{% trans "Console" %}
+
{{ host.console }}
+
+
+ +
+

{% trans "Board Management" %}

+
+
+ {% if host.bm_type %} +
{% trans "Board Management Controller IP Address" %}
+
{{ host.bm_ip }}
+
{% trans "Board Management Controller User Name" %}
+
{{ host.bm_username }}
+ {% endif %} +
+
+ diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_physical_volume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_physical_volume.html new file mode 100755 index 00000000..d94ba4e5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_physical_volume.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Physical Volume Details" %}{% endblock %} + +{% block main %} +

{{ pv.lvm_pv_name }}

+ +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "UUID" %}
+
{{ pv.uuid }}
+
{% trans "State" %}
+
{{ pv.pv_state }}
+
{% trans "Type" %}
+
{{ pv.pv_type }}
+
{% trans "Volume Group Name" %}
+
{{ pv.lvm_vg_name }}
+
+
+
{% trans "Physical Volume UUID" %}
+
{{ pv.lvm_pv_uuid }}
+
{% trans "Physical Volume Name" %}
+
{{ pv.lvm_pv_name }}
+
{% trans "Physical Volume Size" %}
+
{{ pv.lvm_pv_size | filesizeformat }}
+
{% trans "Physical Extents Total" %}
+
{{ pv.lvm_pe_total }}
+
{% trans "Physical Extents Allocated" %}
+
{{ pv.lvm_pe_alloced }}
+
+
+
{% trans "Disk or Partition UUID" %}
+
{{ pv.disk_or_part_uuid }}
+
{% trans "Disk or Partition Node" %}
+
{{ pv.disk_or_part_device_node }}
+
{% trans "Disk or Partition Path" %}
+
{{ pv.disk_or_part_device_path }}
+
+
+
{% trans "Created At" %}
+
{{ pv.created_at }}
+
{% trans "Updated At" %}
+
{{ pv.updated_at }}
+
+
+
+
+{% endblock %} + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_ports.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_ports.html new file mode 100755 index 00000000..b305aa9d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_ports.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ ports_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor.html new file mode 100755 index 00000000..f2cd609c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor.html @@ -0,0 +1,56 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Sensor Details" %}{% endblock %} + +{% block main %} +

{{ sensor.sensorname }}

+ +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "UUID" %}
+
{{ sensor.uuid }}
+
{% trans "Status" %}
+
{{ sensor.status }}
+
{% trans "State" %}
+
{{ sensor.state }}
+
{% trans "SensorType" %}
+
{{ sensor.sensortype }}
+
{% trans "DataType" %}
+
{{ sensor.datatype }}
+
+
+
{% trans "Sensor UUID" %}
+
{{ sensor.uuid }}
+
{% trans "Sensor Name" %}
+
{{ sensor.sensorname }}
+
+
+
{% trans "Suppressed" %}
+
{{ sensor.suppress }}
+
{% trans "Minor Actions" %}
+
{{ sensor.actions_minor }}
+
{% trans "Major Actions" %}
+
{{ sensor.actions_major }}
+
{% trans "Critical Actions" %}
+
{{ sensor.actions_critical }}
+
+
+
{% trans "Sensor Group UUID" %}
+
{{ sensor.sensorgroup_uuid }}
+
+
+
{% trans "Created At" %}
+
{{ sensor.created_at }}
+
{% trans "Updated At" %}
+
{{ sensor.updated_at }}
+
+
+
+
+{% endblock %} + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor_group.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor_group.html new file mode 100755 index 00000000..9ae8fe9f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensor_group.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Sensor Group Details" %}{% endblock %} + +{% block main %} +

{{ sensorgroup.sensorgroupname }}

+ +
+
+
+

{% trans "Info" %}

+
+
+
{% trans "UUID" %}
+
{{ sensorgroup.uuid }}
+
{% trans "Group Name" %}
+
{{ sensorgroup.sensorgroupname }}
+
{% trans "Group Type" %}
+
{{ sensorgroup.sensortype }}
+
{% trans "State" %}
+
{{ sensorgroup.state }}
+
+
+
{% trans "Datatype" %}
+
{{ sensorgroup.datatype }}
+
{% trans "Audit Interval (secs)" %}
+
{{ sensorgroup.audit_interval_group }}
+
{% trans "Algorithm" %}
+
{{ sensorgroup.algorithm }}
+
{% trans "Actions Minor" %}
+
{{ sensorgroup.actions_minor_group }}
+
{% trans "Actions Major" %}
+
{{ sensorgroup.actions_major_group }}
+
{% trans "Actions Critical" %}
+
{{ sensorgroup.actions_critical_group }}
+
+
+
{% trans "Created At" %}
+
{{ sensorgroup.created_at }}
+
{% trans "Updated At" %}
+
{{ sensorgroup.updated_at }}
+
+
+
+
+{% endblock %} + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensors.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensors.html new file mode 100644 index 00000000..313a5de8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_sensors.html @@ -0,0 +1,42 @@ +{% load i18n sizeformat %} + +
+ + {% trans "Sensors" %}: + {{ total }} + + + + {% trans " Suppressed" %}: + {{ suppressed }} + + + + {% trans " Critical" %}: + {{ critical }} + + + + {% trans "Major" %}: + {{ major }} + + + + {% trans "Minor" %}: + {{ minor }} + + +
+ +{% block main %} + {% autoescape off %} +
+ {{ sensorgroups_table.render }} +
+ +
+ {{ sensors_table.render }} +
+ + {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_storages.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_storages.html new file mode 100755 index 00000000..81dc80b3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_detail_storages.html @@ -0,0 +1,28 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ disks_table.render }} +
+ +
+ {{ partitions_table.render }} +
+ + {% if host.personality == "Storage" %} +
+ {{ storagevolumes_table.render }} +
+ {% else %} +
+ {{ localvolumegroups_table.render }} +
+ +
+ {{ physicalvolumes_table.render }} +
+ {% endif %} + + {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_deviceusage.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_deviceusage.html new file mode 100755 index 00000000..b5305cf3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_deviceusage.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ deviceusage_table.render }} +
+ {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_disk_info.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_disk_info.html new file mode 100644 index 00000000..13f5bc47 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_disk_info.html @@ -0,0 +1,30 @@ +{% load i18n %} +{{ disk_info }}{% endblocktrans %} + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles.html new file mode 100755 index 00000000..75e4abc8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles.html @@ -0,0 +1,10 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
+ {{ diskprofiles_table.render }} +
+ {% endautoescape %} +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig.html new file mode 100755 index 00000000..a8895dc7 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig.html @@ -0,0 +1,12 @@ +{% for disk in diskprofile.disks %} +
  • + {{ disk.device_path }} + {% if disk.serial_id %} + {{"("}} {{ disk.serial_id }} {{")"}} + {% endif %} + {% if disk.size_mib %} + {{": "}} {{ disk.size_mib }} + {% endif %} +
  • +{% endfor %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig_obs.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig_obs.html new file mode 100755 index 00000000..d7b14df6 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_diskconfig_obs.html @@ -0,0 +1 @@ +{{ obs_message }} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_lvgconfig.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_lvgconfig.html new file mode 100755 index 00000000..02008e33 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_lvgconfig.html @@ -0,0 +1,15 @@ +{% for lvg in diskprofile.lvgs %} +
  • + {% if lvg.lvm_vg_name == 'nova-local' %} + {{ lvg.lvm_vg_name }} + {% for param in lvg.params %} +
    + {{ param.key }} + {{": "}} + {{ param.value }} + {{ "
    " }} +
    + {% endfor %} + {% endif %} +
  • +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_partitionconfig.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_partitionconfig.html new file mode 100644 index 00000000..1d99c0fd --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_partitionconfig.html @@ -0,0 +1,9 @@ +{% for partition in diskprofile.partitions %} +
  • + {{ partition.device_path }} + {% if partition.size_mib %} + {{": "}} {{ partition.size_mib }} + {% endif %} +
  • +{% endfor %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_storconfig.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_storconfig.html new file mode 100755 index 00000000..1fa58e13 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_diskprofiles_storconfig.html @@ -0,0 +1,25 @@ +{% for stor in diskprofile.stors %} +
  • + {% if stor.osdid %} + {{ stor.osdid }} + {% if stor.function %} + {{": "}} + {% endif %} + {% endif %} + + {% if stor.function %} + {% if stor.function == 'osd' %} + {{ stor.function }} {{"stor"}} + {{"- ceph journal size: "}} {{stor.journal_size_mib}}{{","}} + {% if stor.journal_location == stor.uuid %} + {{ "collocated on osd stor" }} + {% else %} + {{ "on journal stor" }} {{stor.count}} + {% endif %} + {% else %} + {{ stor.function }} {{"stor"}} {{stor.count}} + {% endif %} + {% endif %} + +
  • +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_hosts.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_hosts.html new file mode 100755 index 00000000..8e3c5aef --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_hosts.html @@ -0,0 +1,41 @@ +{% load i18n sizeformat %} + +{% block main %} +
    +
    + + {% for total in totals %} + + {% trans total.name %}: + {{ total.value }} + + {% endfor %} + +
    + +{% if controllers %} +
    + {{ hostscontroller_table.render }} +
    +{% endif %} + +
    +{% if storages %} + {{ hostsstorage_table.render }} +{% endif %} +
    + +
    +{% if computes %} + {{ hostscompute_table.render }} +{% endif %} +
    + +
    +{% if unprovisioned %} + {{ hostsunprovisioned_table.render }} +{% endif %} +
    + +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles.html new file mode 100755 index 00000000..3a4b8f44 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
    + {{ interfaceprofiles_table.render }} +
    + {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_interfaceconfig.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_interfaceconfig.html new file mode 100755 index 00000000..fc183a68 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_interfaceconfig.html @@ -0,0 +1,22 @@ +{% for interfaces in ifProfile.interfaces %} +
  • + {{ interfaces.ifname }} {{": "}} {{ interfaces.networktype }} + {% if interfaces.networktype == 'data' or interfaces.networktype == 'data-external' %} + {{"("}} {{ interfaces.providernetworks }} {{")"}} + {% endif %} + {{ " | " }} {{ interfaces.iftype }} + {% if interfaces.iftype != 'ae' and interfaces.iftype != 'vlan' %} + {{ " | " }}{{"PORTS ="}} {{ interfaces.ports }} + {% endif %} + {% if interfaces.iftype == 'ae' or interfaces.iftype == 'vlan' %} + {{ " | " }}{{"INTERFACES ="}} {{ interfaces.uses }} + {% endif %} + {% if interfaces.iftype == 'ae' %} + {{" | "}} {{ interfaces.aemode }} + {% if interfaces.aemode == 'balanced' %} + {{" | "}} {{ interfaces.txhashpolicy }} + {% endif %} + {% endif %} + {{ " | MTU =" }} {{ interfaces.imtu }} +
  • +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_portconfig.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_portconfig.html new file mode 100755 index 00000000..af34b5cc --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_interfaceprofiles_portconfig.html @@ -0,0 +1,11 @@ +{% for ports in ifProfile.ports %} +
  • + {{ ports.name }} {{": "}} {{ ports.pdevice }} + {% if ports.autoneg != 'na' %} + {{" | Auto Neg ="}} {{ ports.autoneg }} + {% endif %} + {% if ports.bootp %} + {{" | bootp-IF"}} + {% endif %} +
  • +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles.html new file mode 100755 index 00000000..7551f609 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles.html @@ -0,0 +1,9 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
    + {{ memoryprofiles_table.render }} +
    + {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles_memoryassignments.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles_memoryassignments.html new file mode 100755 index 00000000..d77b1f7c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_memoryprofiles_memoryassignments.html @@ -0,0 +1,21 @@ +{% load i18n %} +
    {{ "Platform reserved (MiB): " }}
    +{% for memory in memoryProfile.memory %} +
    + {% trans "Processor" %} {{ memory.numa_node }}: {{ memory.platform_reserved_mib }} {{ "
    " }} +
    +{% endfor %} + +
    {{ "2M hugepages: " }}
    +{% for memory in memoryProfile.memory %} +
    + {% trans "Processor" %} {{ memory.numa_node }}: {{ memory.vm_hugepages_nr_2M_pending }} {{ "
    " }} +
    +{% endfor %} + +
    {{ "1G hugepages: " }}
    +{% for memory in memoryProfile.memory %} +
    + {% trans "Processor" %} {{ memory.numa_node }}: {{ memory.vm_hugepages_nr_1G_pending }} {{ "
    " }} +
    +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_update.html new file mode 100755 index 00000000..ca425e48 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/_update.html @@ -0,0 +1,26 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_host_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:update' host_id %}{% endblock %} + +{% block modal_id %}edit_host_modal{% endblock %} +{% block modal-header %}{% trans "Edit Host" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current host." %}

    +

    {% trans "Note: this will not affect the resources allocated to any existing instances using this host." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/banner.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/banner.html new file mode 100755 index 00000000..135bbd92 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/banner.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block main %} + {% include "horizon/common/_page_header.html" with title=_("banner") %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_cpufunction_processorcores.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_cpufunction_processorcores.html new file mode 100755 index 00000000..7a40616f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_cpufunction_processorcores.html @@ -0,0 +1,5 @@ +{% for s,c in cpufunction.socket_cores.items %} + {% if c %} + {{ "Processor " }} {{ s }} {{ ": " }} {{ c }} {{ "
    " }} + {% endif %} +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_createprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_createprofile.html new file mode 100755 index 00000000..6ef837a1 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_createprofile.html @@ -0,0 +1,42 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load horizon i18n %} + +{% block form_id %}add_cpu_profile_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addcpuprofile' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create CPU Profile" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    + {{ "
    With the following configuration:" }} + + + + + + {% for cpufunc in host.core_assignment %} + + + + + {% endfor %} +
    {% trans "Function" %}{% trans "Processor Logical Cores" %}
    {{ cpu_formats|get_value:cpufunc.allocated_function }} + {% for s,cores in cpufunc.socket_cores.items %} + {% trans "Processor " %} {{ s }} {% trans ": " %} {{ cores }} {{ "
    " }} + {% endfor %} +
    + {{ "
    " }} +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "Create a new CPU Profile based on the cpu core assignments of this host." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_update.html new file mode 100755 index 00000000..deecbfab --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/_update.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_cpufunctions_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editcpufunctions' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Edit CPU Assignments" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current CPU Assignments." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/createprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/createprofile.html new file mode 100755 index 00000000..9fa95be0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/createprofile.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create CPU Profile" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create CPU Profile") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/cpu_functions/_createprofile.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/update.html new file mode 100755 index 00000000..ef7e8ba8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/cpu_functions/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit CPU Assignments" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit CPU Assignments") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/cpu_functions/_update.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/create.html new file mode 100755 index 00000000..537e6cc9 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Host" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Host") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/detail.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/detail.html new file mode 100755 index 00000000..2fad537f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/detail.html @@ -0,0 +1,99 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Inventory Details" %}{% endblock %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} + +{% block js %} + {{ block.super }} + +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_detail_overview.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_detail_overview.html new file mode 100755 index 00000000..0cca4450 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_detail_overview.html @@ -0,0 +1,37 @@ +{% load i18n sizeformat %} + +

    {% trans "Device Overview" %}

    + +
    +
    +
    {% trans "Name" %}
    +
    {{ device.name|default:_("None") }}
    +
    {% trans "PCI Address" %}
    +
    {{ device.pciaddr|default:_("None") }}
    +
    {% trans "Total VFs" %}
    +
    {{ device.sriov_totalvfs|default:_("None") }}
    +
    {% trans "Number of VFs" %}
    +
    {{ device.sriov_numvfs|default:_("None") }}
    +
    {% trans "Numa Node" %}
    +
    {{ device.numa_node|default:_("Disabled") }}
    +
    {% trans "Enabled" %}
    +
    {{ device.enabled|default:_("None") }}
    +
    {% trans "Extra Info" %}
    +
    {{ device.extra_info|default:_("None") }}
    +

    {% trans "Device" %}

    +
    {% trans "Id" %}
    +
    {{ device.pdevice_id|default:_("None") }}
    +
    {% trans "Name" %}
    +
    {{ device.pdevice|default:_("None") }}
    +

    {% trans "Class" %}

    +
    {% trans "Id" %}
    +
    {{ device.pclass_id|default:_("None") }}
    +
    {% trans "Name" %}
    +
    {{ device.pclass|default:_("None") }}
    +

    {% trans "Vendor" %}

    +
    {% trans "Id" %}
    +
    {{ device.pvendor_id|default:_("None") }}
    +
    {% trans "Name" %}
    +
    {{ device.pvendor|default:_("None") }}
    +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_update.html new file mode 100755 index 00000000..63b77e97 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_update.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_device_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editdevice' host_id device_uuid %}{% endblock %} + +{% block modal-header %}{% trans "Edit Device" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current device." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_usage.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_usage.html new file mode 100644 index 00000000..1528d4c9 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/_usage.html @@ -0,0 +1,16 @@ +{% load i18n sizeformat %} + +
    +
    +

    {% trans "VFs" %}

    +
    {% trans "configured" %}
    +
    {{ device_usage.pci_vfs_configured|default:_("None") }}
    +
    {% trans "used" %}
    +
    {{ device_usage.pci_vfs_used|default:_("None") }}
    +

    {% trans "PFs" %}

    +
    {% trans "configured" %}
    +
    {{ device_usage.pci_pfs_configured|default:_("None") }}
    +
    {% trans "used" %}
    +
    {{ device_usage.pci_pfs_used|default:_("None") }}
    +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/detail.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/detail.html new file mode 100755 index 00000000..f9a9a55c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/detail.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Device Detail"%}{% endblock %} + +{% block main %} + {% include "admin/inventory/devices/_detail_overview.html" %} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/update.html new file mode 100644 index 00000000..45fdd519 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Device" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Device") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/devices/_update.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/usage.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/usage.html new file mode 100644 index 00000000..2e0b3f39 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/devices/usage.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Device Usage Detail"%}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Device Usage Detail") %} +{% endblock page_header %} + +{% block main %} +

    {% trans device_usage.device_name %}

    +
    + {% include "admin/inventory/devices/_usage.html" %} +
    +
    + {{ usage_table.render }} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/index.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/index.html new file mode 100755 index 00000000..785975e0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/index.html @@ -0,0 +1,64 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Host Inventory" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Host Inventory") %} +{% endblock page_header %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} + +{% block js %} + {{ block.super }} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_create.html new file mode 100755 index 00000000..8662642f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_create.html @@ -0,0 +1,44 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_interface_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addinterface' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Interface" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can define the configuration of a new interface." %}

    +
    +

    {% trans "Ports & LLDP Neighbors" %}

    + + {% for p in ports %} + + + {% if p.1 %} + + {% else %} + + {% endif %} + + {% endfor %} +
    {{p.0}}
    +
      + {% for n in p.1 %} +
    1. {{n}}
    2. + {% endfor %} +
    +

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_createprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_createprofile.html new file mode 100755 index 00000000..7a6f7217 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_createprofile.html @@ -0,0 +1,61 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_interface_profile_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addinterfaceprofile' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Interface Profile" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    + {{ "
    With the following configuration:" }} + {{ "

    Port Configuration:" }}
    + {% for ports in host.ports %} +
  • + {{ ports.namedisplay }} {{": "}} {{ ports.pdevice }} + {% if ports.autoneg != 'na' %} + {{" | Auto Neg ="}} {{ ports.autoneg }} + {% endif %} + {% if ports.bootp %} + {{" | bootp-IF"}} + {% endif %} +
  • + {% endfor %} + {{ "

    Interface Configuration:" }}
    + {% for interfaces in host.interfaces %} +
  • + {{ interfaces.ifname }} {{": "}} {{ interfaces.networktype }} + {% if interfaces.networktype == 'data' or interface.networktype == 'data-external' %} + {{"("}} {{ interfaces.providernetworks }} {{")"}} + {% endif %} + {{ " | " }} {{ interfaces.iftype }} + {% if interfaces.iftype != 'ae' and interfaces.iftype != 'vlan' %} + {{ " | " }}{{"PORTS ="}} {{ interfaces.ports }} + {% endif %} + {% if interfaces.iftype == 'ae' or interfaces.iftype == 'vlan' %} + {{ " | " }}{{"INTERFACES ="}} {{ interfaces.uses }} + {% endif %} + {% if interfaces.iftype == 'ae' %} + {{" | "}} {{ interfaces.aemode }} + {% if interfaces.aemode == 'balanced' %} + {{" | "}} {{ interfaces.txhashpolicy }} + {% endif %} + {% endif %} + {{" | MTU="}} {{ interfaces.imtu }} +
  • + {% endfor %} + {{ "
    " }} +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "Create a new Interface Profile based on the interface and port configuration of this host." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_detail_overview.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_detail_overview.html new file mode 100755 index 00000000..5c81d23c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_detail_overview.html @@ -0,0 +1,40 @@ +{% load i18n sizeformat %} + +

    {% trans "Interface Overview" %}

    + +
    +
    +
    {% trans "Name" %}
    +
    {{ interface.ifname|default:_("None") }}
    +
    {% trans "Interface Type" %}
    +
    {{ interface.iftype|default:_("None") }}
    + {% if interface.vlan_id %} +
    {% trans "VLAN ID" %}
    +
    {{ interface.vlan_id|default:_("None") }}
    + {% endif %} +
    {% trans "MAC Address" %}
    +
    {{ interface.imac|default:_("None") }}
    +
    {% trans "MTU" %}
    +
    {{ interface.imtu|default:_("None") }}
    +
    {% trans "Network Type" %}
    +
    {{ interface.networktype|default:_("None") }}
    + {% if interface.networktype == "data" or interface.networktype == 'data-external' %} +
    {% trans "Provider Networks" %}
    +
    {{ interface.providernetworks|default:_("None") }}
    +
    {% trans "IPv4 Mode" %}
    +
    {{ interface.ipv4_mode|default:_("Disabled") }}
    + {% if interface.ipv4_mode == "pool" %} +
    {% trans "IPv4 Pool" %}
    +
    {{ interface.ipv4_pool_name|default:_("None") }}
    + {% endif %} +
    {% trans "IPv6 Mode" %}
    +
    {{ interface.ipv6_mode|default:_("Disabled") }}
    + {% if interface.ipv6_mode == "pool" %} +
    {% trans "IPv6 Pool" %}
    +
    {{ interface.ipv6_pool_name|default:_("None") }}
    + {% endif %} + {% endif %} +
    {% trans "Number of Virtual Functions" %}
    +
    {{ interface.sriov_numvfs|default:_("None") }}
    +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_update.html new file mode 100755 index 00000000..756ba382 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/_update.html @@ -0,0 +1,46 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_interface_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editinterface' host_id interface_id %}{% endblock %} + +{% block modal-header %}{% trans "Edit Interface" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current interface." %}

    +
    + {% if ports %} +

    {% trans "Port & LLDP Neighbors" %}

    + + {% for p in ports %} + + + {% if p.1 %} + + {% else %} + + {% endif %} + + {% endfor %} +
    {{p.0}}
    +
      + {% for n in p.1 %} +
    1. {{n}}
    2. + {% endfor %} +
    +

    + {% endif %} +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/_create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/_create.html new file mode 100755 index 00000000..fc888714 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/_create.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_address_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addaddress' host_id interface_id %} +{% endblock %} + +{% block modal-header %}{% trans "Create Address" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create an IP address for a data or infrastructure interface. The address must not overlap with any other address on this same host that are part of the same IP subnet." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/create.html new file mode 100755 index 00000000..ddeaa32e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/address/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Address" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Address") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/interfaces/address/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/create.html new file mode 100755 index 00000000..f5d230bc --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Interface" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Interface") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/interfaces/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/createprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/createprofile.html new file mode 100755 index 00000000..0b7bb834 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/createprofile.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Interface Profile" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Interface Profile") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/interfaces/_createprofile.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/detail.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/detail.html new file mode 100755 index 00000000..72f3f144 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/detail.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Interface Detail"%}{% endblock %} + +{% block main %} + {% include "admin/inventory/interfaces/_detail_overview.html" %} +
    +
    + {{ addresses_table.render }} +
    +
    + {{ routes_table.render }} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/_create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/_create.html new file mode 100755 index 00000000..d7fa7932 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/_create.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_route_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addroute' host_id interface_id %} +{% endblock %} + +{% block modal-header %}{% trans "Create Route" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create an IP route for a data interface. The gateway address must be reachable via one of the local interface addresses."%}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/create.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/create.html new file mode 100755 index 00000000..f776e53a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/route/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Route" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Route") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/interfaces/route/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/update.html new file mode 100755 index 00000000..0fb66d92 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/interfaces/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Interface" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Interface") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/interfaces/_update.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_createprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_createprofile.html new file mode 100755 index 00000000..a2655a85 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_createprofile.html @@ -0,0 +1,43 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_memory_profile_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addmemoryprofile' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Memory Profile" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    + {{ "
    With the following configuration:" }} + + + + + + + {% for node,m in host.numa_nodes %} + + + + + + {% endfor %} +
    {% trans "Numa Node" %}{% trans "Platform Reserved" %}{% trans "VM Hugepages" %}
    {{ node }}{{ m.platform_reserved_mib }} {% trans " MiB " %} + {% trans " 2M Pages: " %} {{ m.vm_hugepages_nr_2M }} {{ "
    " }} + {% trans " 1G Pages: " %} {{ m.vm_hugepages_nr_1G }} {{ "
    " }} +
    + {{ "
    " }} +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "Create a new Memory Profile based on the platform memory and the hugepage assignments on this host." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_edit_hp_memory.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_edit_hp_memory.html new file mode 100755 index 00000000..7b199a8d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_edit_hp_memory.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_hugepage_memory_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:updatememory' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Update Memory Allocation" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the platform reserved memory and the number of Libvirt VM hugepages per numa node." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages.html new file mode 100755 index 00000000..0e46a9f8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages.html @@ -0,0 +1,3 @@ +{{ "Reserved for Platform: " }} {{ memory.platform_reserved_mib }} {{ " MiB" }} {{ "
    " }} +{{ "Usable Total: " }} {{ memory.memtotal_mib }} {{ " MiB" }} {{ "
    " }} +{{ "Available: " }} {{ memory.memavail_mib }} {{ " MiB" }} {{ "
    " }} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages_other.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages_other.html new file mode 100755 index 00000000..e303568e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_memoryfunction_hugepages_other.html @@ -0,0 +1,2 @@ +{{ "Total: " }} {{ memory.memtotal_mib }} {{ " MiB" }} {{ "
    " }} +{{ "Available: " }} {{ memory.memavail_mib }} {{ " MiB" }} {{ "
    " }} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vmfunction_hugepages.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vmfunction_hugepages.html new file mode 100755 index 00000000..07d6effb --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vmfunction_hugepages.html @@ -0,0 +1,24 @@ +{% if memory.hugepages_configured == 'True' %} + + {{ "4K Pages: " }} {{ "
    " }} + {{ "    Total: " }} {{ memory.vm_hugepages_nr_4K }} {{ "
    " }} + + {{ "2M Hugepages: " }} {{ "
    " }} + {% if memory.vm_hugepages_nr_2M_pending or memory.vm_hugepages_nr_2M_pending == 0 %} + {{ "    Total: " }} {{ memory.vm_hugepages_nr_2M }} + {{ "  Pending: "}} {{ memory.vm_hugepages_nr_2M_pending }} {{ "
    " }} + {% else %} + {{ "    Total: " }} {{ memory.vm_hugepages_nr_2M }} {{ "
    " }} + {% endif %} + {{ "    Available: " }} {{ memory.vm_hugepages_avail_2M }} {{ "
    " }} + + {{ "1G Hugepages: " }} {{ "
    " }} + {% if memory.vm_hugepages_nr_1G_pending or memory.vm_hugepages_nr_1G_pending == 0 %} + {{ "    Total: " }} {{ memory.vm_hugepages_nr_1G }} + {{ "  Pending: "}} {{ memory.vm_hugepages_nr_1G_pending }} {{ "
    " }} + {% else %} + {{ "    Total: " }} {{ memory.vm_hugepages_nr_1G }} {{ "
    " }} + {% endif %} + {{ "    Available: " }} {{ memory.vm_hugepages_avail_1G }} {{ "
    " }} + +{% endif %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vswitchfunction_hugepages.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vswitchfunction_hugepages.html new file mode 100755 index 00000000..3345a3f8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/_vswitchfunction_hugepages.html @@ -0,0 +1,5 @@ +{% if memory.hugepages_configured == 'True' %} + {{ "Size: " }} {{ memory.avs_hugepages_size_mib }} {{ " MiB" }} {{ "
    " }} + {{ "Total: " }} {{ memory.avs_hugepages_nr }} {{ "
    " }} + {{ "Available: " }} {{ memory.avs_hugepages_avail }} {{ "
    " }} +{% endif%} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/createprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/createprofile.html new file mode 100755 index 00000000..62bef417 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/createprofile.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Memory Profile" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Memory Profile") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/memory/_createprofile.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/edit_hp_memory.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/edit_hp_memory.html new file mode 100755 index 00000000..5c10c5a6 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/memorys/edit_hp_memory.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Memory" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Memory Allocation") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/memorys/_edit_hp_memory.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_detail_overview.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_detail_overview.html new file mode 100755 index 00000000..f56c17bf --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_detail_overview.html @@ -0,0 +1,36 @@ +{% load i18n sizeformat %} +

    {% trans "Port Overview" %}

    +
    +
    +
    {% trans "Name:" %}
    + {% if port.name %} +
    {{ port.name }}
    + {% else %} +
    {{ port.namedisplay }}
    + {% endif %} +
    {% trans "MAC Address:" %}
    +
    {{ port.mac|default:_("None") }}
    +
    {% trans "PCI Address:" %}
    +
    {{ port.pciaddr|default:_("None") }}
    +
    {% trans "Processor:" %}
    +
    {{ port.numa_node|default:_("None") }}
    +
    {% trans "Auto Negotiation:" %}
    +
    {{ port.autoneg|default:_("None") }}
    +
    {% trans "Number of Virtual Functions:" %}
    +
    {{ port.sriov_numvfs|default:_("None") }}
    +
    {% trans "Max Virtual Functions:" %}
    +
    {{ port.sriov_totalvfs|default:_("None") }}
    +
    {% trans "Virtual Functions PCI Address:" %}
    +
    {{ port.sriov_vfs_pci_address|default:_("None") }}
    +
    {% trans "Driver:" %}
    +
    {{ port.driver|default:_("None") }}
    +
    {% trans "Accelerated:" %}
    +
    {{ port.dpdksupport|default:_("False") }}
    +
    {% trans "Boot Interface:" %}
    + {% if port.bootp %} +
    {{ port.bootp }}
    + {% else %} +
    {{ "False" }}
    + {% endif %} +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_ports_devicetype.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_ports_devicetype.html new file mode 100755 index 00000000..ef8872a3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_ports_devicetype.html @@ -0,0 +1,9 @@ +{% if port.pclass %} + {{ port.pclass }} {{ "
    " }} +{% endif %} +{% if port.pvendor %} + {{ port.pvendor }} {{ "
    " }} +{% endif %} +{% if port.pdevice %} + {{ port.pdevice }} +{% endif %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_update.html new file mode 100755 index 00000000..6331775d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/_update.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_port_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editport' host_id port_id %}{% endblock %} + +{% block modal-header %}{% trans "Edit Port" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current port." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/detail.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/detail.html new file mode 100755 index 00000000..88ce3cc1 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/detail.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Port Detail"%}{% endblock %} + +{% block main %} + {% include "admin/inventory/ports/_detail_overview.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/update.html new file mode 100755 index 00000000..6cff044e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/ports/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Port" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Port") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/ports/_update.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_createsensorgroup.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_createsensorgroup.html new file mode 100755 index 00000000..92887cbc --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_createsensorgroup.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_sensorgroup_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addsensorgroup' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Sensor Group" %}{% endblock %} +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can define the configuration of a new sensor group." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} + + + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensor_actions.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensor_actions.html new file mode 100755 index 00000000..f4a6975a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensor_actions.html @@ -0,0 +1,3 @@ +{{ "Critical: " }} {{ sensor.actions_critical }} {{ "
    " }} +{{ "Major: " }} {{ sensor.actions_major }} {{ "
    " }} +{{ "Minor: " }} {{ sensor.actions_minor }} {{ "
    " }} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensorgroup_actions.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensorgroup_actions.html new file mode 100755 index 00000000..824b79bc --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_sensorgroup_actions.html @@ -0,0 +1,3 @@ +{{ "Critical: " }}{{ sensorgroup.actions_critical_group }} {{ "
    " }} +{{ "Major: " }}{{ sensorgroup.actions_major_group }} {{ "
    " }} +{{ "Minor: " }}{{ sensorgroup.actions_minor_group }} {{ "
    " }} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_updatesensorgroup.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_updatesensorgroup.html new file mode 100755 index 00000000..415c8439 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/_updatesensorgroup.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_sensorgroup_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editsensorgroup' host_id sensorgroup_id %}{% endblock %} + +{% block modal-header %}{% trans "Edit SensorGroup" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current sensorgroup." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/createsensorgroup.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/createsensorgroup.html new file mode 100755 index 00000000..d47b6247 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/createsensorgroup.html @@ -0,0 +1,12 @@ +% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Sensor Group" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Sensor Group") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/sensors/_createsensorgroup.html" %} +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/updatesensorgroup.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/updatesensorgroup.html new file mode 100755 index 00000000..c6056e3e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/sensors/updatesensorgroup.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit SensorGroup" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit SensorGroup") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/sensors/_updatesensorgroup.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_creatediskprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_creatediskprofile.html new file mode 100755 index 00000000..687cf0de --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_creatediskprofile.html @@ -0,0 +1,93 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_disk_profile_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:adddiskprofile' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Storage Profile" %}{% endblock %} + +{% block modal-body %} + +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    + +
    +

    {% trans "Description" %}:

    +

    {% trans "Create a new Storage Profile based on the storage and disk configuration of this host." %}

    +
    + + {{ "
    With the following configuration:" }} + + {% if host.stors and 'storage' in host.subfunctions %} + + + + + {% for disk in host.disks %} + + + {% for stor in host.stors %} + {% if stor.uuid == disk.istor_uuid %} + + {% endif %} + {% endfor %} + + + {% endfor %} + {% endif %} + + + + {% if host.lvgs %} + + + + {% endif %} + + + + {% if host.lvgs %} + {% for lvg in host.lvgs %} + + + {% endfor %} + + {% endif %} + +
    {% trans "Disk" %}{% trans "Storage" %}
    {{ disk.device_path }} {{"( "}} {{ disk.device_node }}{{", "}} {{disk.serial_id}} {{" )"}} {{": "}} {{ disk.size_mib }} {{ "MiB" }} + {% if stor.function == 'osd' %} + {{ stor.function }} {{"stor"}} + {{"- ceph journal size: "}} {{stor.journal_size_mib}}{{","}} + {% if stor.journal_location == stor.uuid %} + {{ "collocated on osd stor" }} + {% else %} + {{ "on journal stor" }} {{stor.count}} + {% endif %} + {% else %} + {{ stor.function }} {{"stor"}} {{stor.count}} + {% endif %} +
    {% trans "Logical Volume Group" %}{% trans "Physical Volumes" %}{% trans "Physical Volume Partitions" %}
    {{ "name"}} {{": "}} {{ lvg.lvm_vg_name }} + {{ "
    " }} + {{ "instance backing"}} {{": "}} {{lvg.instance_backing }} + {{ "
    " }} + {% if lvg.instances_lv_size_mib %} + {{ "instances_lv size MiB"}} {{": "}} {{lvg.instances_lv_size_mib }} + {{ "
    " }} + {% endif %} + {{ "concurrent disk operations"}} {{": "}} {{lvg.concurrent_disk_operations }}
    {{ "devices"}} {{": "}} {{ lvg.dev_paths }}{% for part in host.partitions %} + {{ part.device_path}} {{": "}} {{ part.size_mib }} {{" MiB"}} + {{ "
    " }} + {% endfor %} +
    + {{ "
    " }} + +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createlocalvolumegroup.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createlocalvolumegroup.html new file mode 100755 index 00000000..7a0ef9b1 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createlocalvolumegroup.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_localvolumegroup_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addlocalvolumegroup' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Local Volume Group" %}{% endblock %} +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can define the configuration of a new local volume group." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} + + + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createpartition.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createpartition.html new file mode 100644 index 00000000..716fce4c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createpartition.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_partition_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:createpartition' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Partition" %}{% endblock %} +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can create a new partition." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createphysicalvolume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createphysicalvolume.html new file mode 100755 index 00000000..6c2b5cee --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createphysicalvolume.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_physicalvolume_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addphysicalvolume' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Create Physical Volume" %}{% endblock %} +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can define the configuration of a new physical volume." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} + + + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createstoragevolume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createstoragevolume.html new file mode 100755 index 00000000..4e71e4ba --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_createstoragevolume.html @@ -0,0 +1,62 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_storagevolume_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:addstoragevolume' host_id %}{% endblock %} + +{% block modal-header %}{% trans "Assign Storage Function" %}{% endblock %} +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    + +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can define the configuration of a new storage volume." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editpartition.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editpartition.html new file mode 100644 index 00000000..6ba832ff --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editpartition.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_partition_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editpartition' host_id partition_uuid %}{% endblock %} + +{% block modal-header %}{% trans "Edit Partition" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can enlarge the current partition." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editstoragevolume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editstoragevolume.html new file mode 100755 index 00000000..8ba25997 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/_editstoragevolume.html @@ -0,0 +1,44 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_storagevolume_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:editstoragevolume' host_id stor_uuid %}{% endblock %} + +{% block modal-header %}{% trans "Edit Storage Volume" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    + +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can edit the configuration of an existing storage volume." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/creatediskprofile.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/creatediskprofile.html new file mode 100755 index 00000000..118c19d9 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/creatediskprofile.html @@ -0,0 +1,12 @@ +% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Storage Profile" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Storage Profile") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_creatediskprofile.html" %} +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createlocalvolumegroup.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createlocalvolumegroup.html new file mode 100755 index 00000000..56eea9ef --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createlocalvolumegroup.html @@ -0,0 +1,12 @@ +% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Local Volume Group" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Local Volume Group") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_createlocalvolumegroup.html" %} +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createpartition.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createpartition.html new file mode 100644 index 00000000..c002ca48 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createpartition.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %} {% trans "Create Partition" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Partition") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_createpartition.html" %} +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createphysicalvolume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createphysicalvolume.html new file mode 100755 index 00000000..21e6bf61 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createphysicalvolume.html @@ -0,0 +1,12 @@ +% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Local Volume Group" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Physical Volume") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_createphysicalvolume.html" %} +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createstoragevolume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createstoragevolume.html new file mode 100755 index 00000000..1668e133 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/createstoragevolume.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %} {% trans "Create Storage Volume" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Storage Volume") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_createstoragevolume.html" %} +{% endblock %} + + + + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editpartition.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editpartition.html new file mode 100644 index 00000000..579ea222 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editpartition.html @@ -0,0 +1,12 @@ + +{% extends 'base.html' %} +{% load i18n %} +{% block title %} {% trans "Edit Partition" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Partition") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_editpartition.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editstoragevolume.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editstoragevolume.html new file mode 100755 index 00000000..36a320b2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/editstoragevolume.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %} {% trans "Edit Storage Volume" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Storage Volume") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/_editstoragevolume.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/_edit.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/_edit.html new file mode 100755 index 00000000..ad62ccec --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/_edit.html @@ -0,0 +1,44 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n horizon humanize %} + +{% block form_id %}param_edit_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:inventory:storages:lvg:edit' lvg.uuid key %}{% endblock %} + + +{% block modal_id %}param_edit_modal{% endblock %} +{% block modal-header %}{% trans "Edit Local Volume Group Parameter" %}{% endblock %} + +{% block modal-body %} +

    {% trans "Local Volume Group" %}: {{ lvg.lvm_vg_name }}

    +{% if free %} +
    +
    + {% trans "Instances logical volume allocation" %} + {% blocktrans with a=used|intcomma b=total|intcomma %}

    {{ a }} MiB of {{ b }} MiB Used

    {% endblocktrans %}
    +
    +
    + {% trans "Volume group space available for instance disks" %} + {% blocktrans with a=free|intcomma b=total|intcomma %}

    {{ a }} MiB of {{ b }} MiB Free

    {% endblocktrans %}
    +
    +
    + {% trans "Acceptable instances logical volume size" %} + {% blocktrans with a=allowed_min|intcomma b=allowed_max|intcomma %}

    {{ a }} MiB to {{ b }} MiB

    {% endblocktrans %}
    +
    +
    +{% endif %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description " %}:

    +

    {% trans 'Update a parameter key-value pair for a Local Volume Group.' %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/edit.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/edit.html new file mode 100644 index 00000000..c066187e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/storages/lvg/edit.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Edit Local Volume Group Parameter" %}{% endblock %} + +{% block page_header %} +

    {% trans "Local Volume Group" %}: {{lvg.lvm_vg_name}}

    +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/storages/lvg/_edit.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/update.html b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/update.html new file mode 100755 index 00000000..b87a654b --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/templates/inventory/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Host" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Host") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/inventory/_update.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/inventory/urls.py b/cgcs_dashboard/dashboards/admin/inventory/urls.py new file mode 100755 index 00000000..694b1811 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/urls.py @@ -0,0 +1,157 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from django.conf.urls import include # noqa +from django.conf.urls import url + +from openstack_dashboard.dashboards.admin.inventory.cpu_functions import \ + views as cpu_functions_views +from openstack_dashboard.dashboards.admin.inventory.devices import \ + views as device_views +from openstack_dashboard.dashboards.admin.inventory.interfaces.address import \ + views as address_views +from openstack_dashboard.dashboards.admin.inventory.interfaces.route import \ + views as route_views +from openstack_dashboard.dashboards.admin.inventory.interfaces import \ + views as interface_views +from openstack_dashboard.dashboards.admin.inventory.lldp import \ + views as lldp_views +from openstack_dashboard.dashboards.admin.inventory.memorys import \ + views as memory_views +from openstack_dashboard.dashboards.admin.inventory.ports import \ + views as port_views +from openstack_dashboard.dashboards.admin.inventory.sensors import \ + views as sensor_views +from openstack_dashboard.dashboards.admin.inventory.storages import \ + views as storage_views +from openstack_dashboard.dashboards.admin.inventory.views import \ + AddView +from openstack_dashboard.dashboards.admin.inventory.views import \ + DetailView +from openstack_dashboard.dashboards.admin.inventory.views import \ + IndexView +from openstack_dashboard.dashboards.admin.inventory.views import \ + UpdateView + +from openstack_dashboard.dashboards.admin.inventory.storages \ + import urls as storages_urls + + +urlpatterns = [ + url(r'^$', IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/detail/$', + DetailView.as_view(), name='detail'), + url(r'^create/$', AddView.as_view(), name='create'), + url(r'^(?P[^/]+)/update/$', + UpdateView.as_view(), name='update'), + url(r'^(?P[^/]+)/editcpufunctions/$', + cpu_functions_views.UpdateCpuFunctionsView.as_view(), + name='editcpufunctions'), + url(r'^(?P[^/]+)/addcpuprofile/$', + cpu_functions_views.AddCpuProfileView.as_view(), + name='addcpuprofile'), + url(r'^(?P[^/]+)/addinterface/$', + interface_views.AddInterfaceView.as_view(), + name='addinterface'), + url(r'^(?P[^/]+)/addinterfaceprofile/$', + interface_views.AddInterfaceProfileView.as_view(), + name='addinterfaceprofile'), + url( + r'^(?P[^/]+)/interfaces/(?P[^/]+)/update/$', + interface_views.UpdateView.as_view(), + name='editinterface'), + url( + r'^(?P[^/]+)/interfaces/(?P[^/]+)/detail/$', + interface_views.DetailView.as_view(), + name='viewinterface'), + url( + r'^(?P[^/]+)/interfaces/(?P[^/]+)/addaddress/$', + address_views.CreateView.as_view(), + name='addaddress'), + url( + r'^(?P[^/]+)/interfaces/(?P[^/]+)/addroute/$', + route_views.CreateView.as_view(), + name='addroute'), + url( + r'^(?P[^/]+)/ports/(?P[^/]+)/update/$', + port_views.UpdateView.as_view(), name='editport'), + url( + r'^(?P[^/]+)/ports/(?P[^/]+)/detail/$', + port_views.DetailView.as_view(), + name='viewport'), + url(r'^(?P[^/]+)/addstoragevolume/$', + storage_views.AddStorageVolumeView.as_view(), + name='addstoragevolume'), + url(r'^(?P[^/]+)/adddiskprofile/$', + storage_views.AddDiskProfileView.as_view(), + name='adddiskprofile'), + url(r'^(?P[^/]+)/updatememory/$', + memory_views.UpdateMemoryView.as_view(), + name='updatememory'), + url(r'^(?P[^/]+)/addmemoryprofile/$', + memory_views.AddMemoryProfileView.as_view(), + name='addmemoryprofile'), + + url(r'^(?P[^/]+)/addlocalvolumegroup/$', + storage_views.AddLocalVolumeGroupView.as_view(), + name='addlocalvolumegroup'), + url(r'^(?P[^/]+)/addphysicalvolume/$', + storage_views.AddPhysicalVolumeView.as_view(), + name='addphysicalvolume'), + url(r'^(?P[^/]+)/physicalvolumedetail/$', + storage_views.DetailPhysicalVolumeView.as_view(), + name='physicalvolumedetail'), + url(r'^(?P[^/]+)/localvolumegroupdetail/$', + storage_views.DetailLocalVolumeGroupView.as_view(), + name='localvolumegroupdetail'), + url(r'^(?P[^/]+)/storages/', + include(storages_urls, namespace='storages')), + + url(r'^(?P[^/]+)/addsensorgroup/$', + sensor_views.AddSensorGroupView.as_view(), + name='addsensorgroup'), + url(r'^(?P[^/]+)/sensorgroups/(?P[^/]+)/' + 'updatesensorgroup/$', + sensor_views.UpdateSensorGroupView.as_view(), + name='editsensorgroup'), + url(r'^(?P[^/]+)/sensordetail/$', + sensor_views.DetailSensorView.as_view(), + name='sensordetail'), + url(r'^(?P[^/]+)/sensorgroupdetail/$', + sensor_views.DetailSensorGroupView.as_view(), + name='sensorgroupdetail'), + + url( + r'^(?P[^/]+)/devices/(?P[^/]+)/update/$', + device_views.UpdateView.as_view(), + name='editdevice'), + url( + r'^(?P[^/]+)/devices/(?P[^/]+)/detail/$', + device_views.DetailView.as_view(), + name='viewdevice'), + url( + r'^(?P[^/]+)/devices/usage/$', + device_views.UsageView.as_view(), + name='viewusage'), + url( + r'^(?P[^/]+)/viewneighbour/$', + lldp_views.DetailNeighbourView.as_view(), name='viewneighbour'), + url(r'^(?P[^/]+)/storages/(?P[^/]+)/' + 'editstoragevolume/$', + storage_views.EditStorageVolumeView.as_view(), + name='editstoragevolume'), + url(r'^(?P[^/]+)/createpartition/$', + storage_views.CreatePartitionView.as_view(), + name='createpartition'), + url(r'^(?P[^/]+)/storages/(?P[^/]+)/' + 'editpartition/$', + storage_views.EditPartitionView.as_view(), + name='editpartition') +] diff --git a/cgcs_dashboard/dashboards/admin/inventory/views.py b/cgcs_dashboard/dashboards/admin/inventory/views.py new file mode 100755 index 00000000..e84fa4f5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/views.py @@ -0,0 +1,196 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +import cpu_functions.utils as icpu_utils +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs +from horizon import workflows +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.inventory.tabs import HostDetailTabs +from openstack_dashboard.dashboards.admin.inventory.tabs import InventoryTabs +from openstack_dashboard.dashboards.admin.inventory.workflows import AddHost +from openstack_dashboard.dashboards.admin.inventory.workflows import UpdateHost + + +LOG = logging.getLogger(__name__) + + +class IndexView(tabs.TabbedTableView): + tab_group_class = InventoryTabs + template_name = 'admin/inventory/index.html' + page_title = _("Host Inventory") + + def get_tabs(self, request, *args, **kwargs): + return self.tab_group_class(request, **kwargs) + + +class AddView(workflows.WorkflowView): + workflow_class = AddHost + template_name = 'admin/inventory/create.html' + success_url = reverse_lazy('horizon:admin:inventory:index') + + def get_initial(self): + + return {'hostname': "", + 'personality': "", + 'subfunctions': "", + 'mgmt_mac': "", + 'bm_type': api.sysinv.BM_TYPE_NULL, + 'bm_ip': "", + 'bm_username': ""} + + +class UpdateView(workflows.WorkflowView): + workflow_class = UpdateHost + template_name = 'admin/inventory/update.html' + success_url = reverse_lazy('horizon:admin:inventory:index') + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + context['host_id'] = self.kwargs['host_id'] + return context + + def get_initial(self): + try: + host = api.sysinv.host_get(self.request, self.kwargs['host_id']) + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve host data.")) + + return {'host_id': host.id, + 'hostname': host.hostname, + 'personality': host._personality, + 'subfunctions': host._subfunctions, + 'location': host.location, + 'bm_type': host.bm_type, + 'bm_ip': host.bm_ip, + 'bm_username': host.bm_username, + 'ttys_dcd': host.ttys_dcd, + 'pers_subtype': getattr(host, + 'capabilities', + {}).get('pers_subtype', "")} + + +class DetailView(tabs.TabbedTableView): + tab_group_class = HostDetailTabs + template_name = 'admin/inventory/detail.html' + page_title = '{{ _("Host Detail: ")|add:host.hostname' \ + '|default:_("Unprovisioned Host Detail") }}' + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + context["host"] = self.get_data() + return context + + # Make the LVG/PV state data clearer to the end user + def _adjust_state_data(self, state, name): + adjustment = '' + if state in [api.sysinv.PV_ADD, api.sysinv.PV_DEL]: + if name == api.sysinv.LVG_CGTS_VG: + adjustment = ' (now)' + elif name == api.sysinv.LVG_CINDER_VOLUMES: + adjustment = ' (with backend)' + elif name == api.sysinv.LVG_NOVA_LOCAL: + adjustment = ' (on unlock)' + return state + adjustment + + def get_data(self): + if not hasattr(self, "_host"): + host_id = self.kwargs['host_id'] + try: + host = api.sysinv.host_get(self.request, host_id) + + host.nodes = api.sysinv.host_node_list(self.request, host.uuid) + host.cpus = api.sysinv.host_cpu_list(self.request, host.uuid) + icpu_utils.restructure_host_cpu_data(host) + + host.memorys = api.sysinv.host_memory_list(self.request, + host.uuid) + for m in host.memorys: + for n in host.nodes: + if m.inode_uuid == n.uuid: + m.numa_node = n.numa_node + break + + host.ports = api.sysinv.host_port_list(self.request, host.uuid) + host.interfaces = api.sysinv.host_interface_list(self.request, + host.uuid) + host.devices = api.sysinv.host_device_list(self.request, + host.uuid) + host.disks = api.sysinv.host_disk_list(self.request, host.uuid) + host.stors = api.sysinv.host_stor_list(self.request, host.uuid) + host.pvs = api.sysinv.host_pv_list(self.request, host.uuid) + host.partitions = api.sysinv.host_disk_partition_list( + self.request, host.uuid) + + # Translate partition state codes: + for p in host.partitions: + p.status = api.sysinv.PARTITION_STATUS_MSG[p.status] + + host.lldpneighbours = \ + api.sysinv.host_lldpneighbour_list(self.request, host.uuid) + + # Set the value for neighbours field for each port in the host. + # This will be referenced in Interfaces table + for p in host.ports: + p.neighbours = \ + [n.port_identifier for n in host.lldpneighbours + if n.port_uuid == p.uuid] + + # Adjust pv state to be more "user friendly" + for pv in host.pvs: + pv.pv_state = self._adjust_state_data(pv.pv_state, + pv.lvm_vg_name) + + host.lvgs = api.sysinv.host_lvg_list(self.request, host.uuid, + get_params=True) + + # Adjust lvg state to be more "user friendly" + for lvg in host.lvgs: + lvg.vg_state = self._adjust_state_data(lvg.vg_state, + lvg.lvm_vg_name) + + host.sensors = api.sysinv.host_sensor_list(self.request, + host.uuid) + host.sensorgroups = api.sysinv.host_sensorgroup_list( + self.request, host.uuid) + + # Add patching status data to hosts + phost = api.patch.get_host(self.request, host.hostname) + if phost is not None: + if phost.interim_state is True: + host.patch_current = "Pending" + elif phost.patch_failed is True: + host.patch_current = "Failed" + else: + host.patch_current = phost.patch_current + host.requires_reboot = "Yes" if phost.requires_reboot \ + else "No" + host._patch_state = phost.state + host.allow_insvc_patching = phost.allow_insvc_patching + + except Exception: + redirect = reverse('horizon:admin:inventory:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'host "%s".') % host_id, + redirect=redirect) + self._host = host + return self._host + + def get_tabs(self, request, *args, **kwargs): + host = self.get_data() + return self.tab_group_class(request, host=host, **kwargs) diff --git a/cgcs_dashboard/dashboards/admin/inventory/workflows.py b/cgcs_dashboard/dashboards/admin/inventory/workflows.py new file mode 100755 index 00000000..23f26bd9 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/inventory/workflows.py @@ -0,0 +1,839 @@ +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +import cpu_functions.utils as icpu_utils + +from cgtsclient.common import constants +from cgtsclient import exc +from django.utils.translation import ugettext_lazy as _ # noqa +from django.views.decorators.debug import sensitive_variables # noqa + +from horizon import exceptions +from horizon import forms +from horizon.utils import validators +from horizon import workflows +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + +PERSONALITY_CHOICES = ( + (api.sysinv.PERSONALITY_COMPUTE, _("Compute")), + (api.sysinv.PERSONALITY_CONTROLLER, _("Controller")), + (api.sysinv.PERSONALITY_STORAGE, _("Storage")), +) + +FIELD_LABEL_PERFORMANCE_PROFILE = _("Performance Profile") +PERFORMANCE_CHOICES = ( + (api.sysinv.SUBFUNCTIONS_COMPUTE, _("Standard")), + (api.sysinv.SUBFUNCTIONS_COMPUTE + ',' + + api.sysinv.SUBFUNCTIONS_LOWLATENCY, _("Low Latency")), +) + +PERSONALITY_CHOICES_WITHOUT_STORAGE = ( + (api.sysinv.PERSONALITY_COMPUTE, _("Compute")), + (api.sysinv.PERSONALITY_CONTROLLER, _("Controller")), +) + +PERSONALITY_CHOICE_CONTROLLER = ( + (api.sysinv.PERSONALITY_CONTROLLER, _("Controller")), +) + +BM_TYPES_CHOICES = ( + (api.sysinv.BM_TYPE_NULL, _('No Board Management')), + (api.sysinv.BM_TYPE_GENERIC, _("Board Management Controller")), +) + + +def ifprofile_applicable(host, profile): + for interface in profile.interfaces: + if (api.sysinv.PERSONALITY_COMPUTE == host._personality and + interface.networktype == 'oam'): + return False + if (api.sysinv.PERSONALITY_COMPUTE not in host._subfunctions and + interface.networktype == 'data'): + return False + return True + + +def cpuprofile_applicable(host, profile): + if (host.sockets == profile.sockets and + host.physical_cores == profile.physical_cores and + host.hyperthreading == profile.hyperthreading): + errorstring = icpu_utils.check_core_functions(host.subfunctions, + profile.cpus) + if not errorstring: + return True + return False + + +def diskprofile_applicable(host, diskprofile): + # if host contain sufficient number of disks for diskprofile + if not len(host.disks) >= len(diskprofile.disks): + return False + + if api.sysinv.PERSONALITY_COMPUTE in host._subfunctions: + if diskprofile.lvgs: + for lvg in diskprofile.lvgs: + if (hasattr(lvg, 'lvm_vg_name') and + 'nova-local' in lvg.lvm_vg_name): + return True + else: + return False + else: + return False + elif api.sysinv.PERSONALITY_STORAGE in host._subfunctions: + if diskprofile.stors: + if (host.capabilities.get('pers_subtype') == + api.sysinv.PERSONALITY_SUBTYPE_CEPH_CACHING): + for pstor in diskprofile.stors: + if pstor.function == 'journal': + return False + return True + else: + return False + + return True + + +def memoryprofile_applicable(host, personality, profile): + # If profile has more than in host + if not len(host.memory) >= len(profile.memory): + return False + if len(host.nodes) != len(profile.nodes): + return False + if 'compute' not in personality: + return False + return True + + +def profile_get_uuid(request, profilename): + ifprofiles = api.sysinv.host_interfaceprofile_list(request) + cpuprofiles = api.sysinv.host_cpuprofile_list(request) + storprofiles = api.sysinv.host_diskprofile_list(request) + memoryprofiles = api.sysinv.host_memprofile_list(request) + + profiles = ifprofiles + cpuprofiles + storprofiles + memoryprofiles + + for iprofile in profiles: + if iprofile.profilename == profilename: + break + else: + raise forms.ValidationError("Profile not found: %s" % profilename) + return iprofile.uuid + + +class AddHostInfoAction(workflows.Action): + FIELD_LABEL_PERSONALITY = _("Personality") + FIELD_LABEL_HOSTNAME = _("Host Name") + FIELD_LABEL_MGMT_MAC = _("Management MAC Address") + FIELD_LABEL_MGMT_IP = _("Management IP Address") + + personality = forms.ChoiceField(label=FIELD_LABEL_PERSONALITY, + help_text=_("Host Personality"), + choices=PERSONALITY_CHOICES, + widget=forms.Select( + attrs={'class': 'switchable', + 'data-slug': 'personality'})) + + subfunctions = forms.ChoiceField( + label=FIELD_LABEL_PERFORMANCE_PROFILE, + choices=PERFORMANCE_CHOICES, + widget=forms.Select( + attrs={'class': 'switched', + 'data-switch-on': 'personality', + 'data-personality-' + + api.sysinv.PERSONALITY_COMPUTE: _( + "Personality Sub-Type")})) + + hostname = forms.RegexField(label=FIELD_LABEL_HOSTNAME, + max_length=255, + required=False, + regex=r'^[\w\.\-]+$', + error_messages={ + 'invalid': + _('Name may only contain letters,' + ' numbers, underscores, ' + 'periods and hyphens.')}, + widget=forms.TextInput( + attrs={'class': 'switched', + 'data-switch-on': 'personality', + 'data-personality-' + + api.sysinv.PERSONALITY_COMPUTE: + FIELD_LABEL_HOSTNAME, + })) + + mgmt_mac = forms.MACAddressField( + label=FIELD_LABEL_MGMT_MAC, + widget=forms.TextInput( + attrs={'class': 'switched', + 'data-switch-on': 'personality', + 'data-personality-' + + api.sysinv.PERSONALITY_COMPUTE: FIELD_LABEL_MGMT_MAC, + 'data-personality-' + + api.sysinv.PERSONALITY_CONTROLLER: FIELD_LABEL_MGMT_MAC, + 'data-personality-' + + api.sysinv.PERSONALITY_STORAGE: FIELD_LABEL_MGMT_MAC, + })) + + class Meta(object): + name = _("Host Info") + help_text = _( + "From here you can add the configuration for a new host.") + + def __init__(self, request, *arg, **kwargs): + super(AddHostInfoAction, self).__init__(request, *arg, **kwargs) + + # pesonality cannot be storage if ceph is not configured + cinder_backend = api.sysinv.get_cinder_backend(request) + if api.sysinv.CINDER_BACKEND_CEPH not in cinder_backend: + self.fields['personality'].choices = \ + PERSONALITY_CHOICES_WITHOUT_STORAGE + + # All-in-one system, personality can only be controller. + systems = api.sysinv.system_list(request) + system_type = systems[0].to_dict().get('system_type') + if system_type == constants.TS_AIO: + self.fields['personality'].choices = \ + PERSONALITY_CHOICE_CONTROLLER + self.fields['personality'].widget.attrs['disabled'] = 'disabled' + + def clean(self): + cleaned_data = super(AddHostInfoAction, self).clean() + return cleaned_data + + +class UpdateHostInfoAction(workflows.Action): + host_id = forms.CharField(widget=forms.widgets.HiddenInput) + + personality = forms.ChoiceField(label=_("Personality"), + choices=PERSONALITY_CHOICES, + widget=forms.Select( + attrs={'class': 'switchable', + 'data-slug': 'personality'})) + + subfunctions = forms.ChoiceField( + label=FIELD_LABEL_PERFORMANCE_PROFILE, + choices=PERFORMANCE_CHOICES, + widget=forms.Select( + attrs={'class': 'switched', + 'data-switch-on': 'personality', + 'data-personality-' + + api.sysinv.PERSONALITY_COMPUTE: _( + "Performance Profile")})) + + pers_subtype = forms.ChoiceField( + label=_("Personality Sub-Type"), + choices=api.sysinv.Host.SUBTYPE_CHOICES, + widget=forms.Select( + attrs={'class': 'switched', + 'data-switch-on': 'personality', + 'data-personality-' + + api.sysinv.PERSONALITY_STORAGE: _( + "Personality Sub-Type")})) + + hostname = forms.RegexField(label=_("Host Name"), + max_length=255, + required=False, + regex=r'^[\w\.\-]+$', + error_messages={ + 'invalid': + _('Name may only contain letters,' + ' numbers, underscores, ' + 'periods and hyphens.')}, + widget=forms.TextInput( + attrs={'class': 'switched', + 'data-switch-on': 'personality', + 'data-personality-' + + api.sysinv.PERSONALITY_COMPUTE: _( + "Host Name")})) + + location = forms.CharField(label=_("Location"), + initial='location', + required=False, + help_text=_("Physical location of Host.")) + + cpuProfile = forms.ChoiceField(label=_("CPU Profile"), + required=False) + + interfaceProfile = forms.ChoiceField(label=_("Interface Profile"), + required=False) + + diskProfile = forms.ChoiceField(label=_("Storage Profile"), + required=False) + + memoryProfile = forms.ChoiceField(label=_("Memory Profile"), + required=False) + + ttys_dcd = forms.BooleanField( + label=_("Serial Console Data Carrier Detect"), + required=False, + help_text=_("Enable serial line data carrier detection. " + "When selected, dropping carrier detect on the serial " + "port revoke any active session and a new login " + "process is initiated when a new connection is detected.")) + + class Meta(object): + name = _("Host Info") + help_text = _( + "From here you can update the configuration of the current host.\n" + "Note: this will not affect the resources allocated to any" + " existing" + " instances using this host until the host is rebooted.") + + def __init__(self, request, *args, **kwargs): + super(UpdateHostInfoAction, self).__init__(request, *args, **kwargs) + + # pesonality cannot be storage if ceph is not configured + cinder_backend = api.sysinv.get_cinder_backend(request) + if api.sysinv.CINDER_BACKEND_CEPH not in cinder_backend: + self.fields['personality'].choices = \ + PERSONALITY_CHOICES_WITHOUT_STORAGE + + # All-in-one system, personality can only be controller. + systems = api.sysinv.system_list(request) + self.system_mode = systems[0].to_dict().get('system_mode') + self.system_type = systems[0].to_dict().get('system_type') + if self.system_type == constants.TS_AIO: + self.fields['personality'].choices = \ + PERSONALITY_CHOICE_CONTROLLER + self.fields['personality'].widget.attrs['disabled'] = 'disabled' + + # hostname cannot be modified once it is set + if self.initial['hostname']: + self.fields['hostname'].widget.attrs['readonly'] = 'readonly' + self.fields['hostname'].required = False + + # subfunctions cannot be modified once it is set + if self.initial['subfunctions']: + self.fields['subfunctions'].widget.attrs['disabled'] = 'disabled' + self.fields['subfunctions'].required = False + + # personality cannot be modified once it is set + host_id = self.initial['host_id'] + personality = self.initial['personality'] + + if (api.sysinv.CINDER_BACKEND_CEPH not in api.sysinv.get_cinder_backend(request)) \ + or personality: + self.fields['pers_subtype'].widget.attrs['disabled'] = 'disabled' + self.fields['pers_subtype'].required = False + + mem_profile_configurable = False + + if personality and self.system_mode != constants.SYSTEM_MODE_SIMPLEX: + self.fields['personality'].widget.attrs['disabled'] = 'disabled' + self.fields['personality'].required = False + self._personality = personality + + host = api.sysinv.host_get(self.request, host_id) + host.nodes = api.sysinv.host_node_list(self.request, host.uuid) + host.cpus = api.sysinv.host_cpu_list(self.request, host.uuid) + host.ports = api.sysinv.host_port_list(self.request, host.uuid) + host.disks = api.sysinv.host_disk_list(self.request, host.uuid) + + if 'compute' in host.subfunctions: + mem_profile_configurable = True + host.memory = api.sysinv.host_memory_list( + self.request, host.uuid) + else: + del self.fields['memoryProfile'] + + if host.nodes and host.cpus and host.ports: + # Populate Available Cpu Profile Choices + try: + avail_cpu_profile_list = api.sysinv.host_cpuprofile_list( + self.request) + + host_profile = icpu_utils.HostCpuProfile( + host.subfunctions, + host.cpus, host.nodes) + + cpu_profile_tuple_list = [ + ('', _("Copy from an available cpu profile."))] + for ip in avail_cpu_profile_list: + nodes = api.sysinv.host_node_list(self.request, + ip.uuid) + cpu_profile = icpu_utils.CpuProfile(ip.cpus, nodes) + if host_profile.profile_applicable(cpu_profile): + cpu_profile_tuple_list.append( + (ip.profilename, ip.profilename)) + + except Exception: + exceptions.handle(self.request, _( + 'Unable to retrieve list of cpu profiles.')) + cpu_profile_tuple_list = [] + + self.fields['cpuProfile'].choices = cpu_profile_tuple_list + + # Populate Available Interface Profile Choices + try: + avail_interface_profile_list = \ + api.sysinv.host_interfaceprofile_list(self.request) + + interface_profile_tuple_list = [ + ('', _("Copy from an available interface profile."))] + for ip in avail_interface_profile_list: + if ifprofile_applicable(host, ip): + interface_profile_tuple_list.append( + (ip.profilename, ip.profilename)) + + except Exception: + exceptions.handle(self.request, _( + 'Unable to retrieve list of interface profiles.')) + interface_profile_tuple_list = [] + + self.fields[ + 'interfaceProfile'].choices = interface_profile_tuple_list + else: + self.fields['cpuProfile'].widget = forms.widgets.HiddenInput() + self.fields[ + 'interfaceProfile'].widget = forms.widgets.HiddenInput() + + if ((personality == 'storage' or 'compute' in host._subfunctions) + and host.disks): + # Populate Available Disk Profile Choices + try: + disk_profile_tuple_list = [ + ('', _("Copy from an available storage profile."))] + avail_disk_profile_list = \ + api.sysinv.host_diskprofile_list(self.request) + for dp in avail_disk_profile_list: + if diskprofile_applicable(host, dp): + disk_profile_tuple_list.append( + (dp.profilename, dp.profilename)) + + except Exception as e: + LOG.exception(e) + exceptions.handle(self.request, _( + 'Unable to retrieve list of storage profiles.')) + disk_profile_tuple_list = [] + + self.fields['diskProfile'].choices = disk_profile_tuple_list + else: + self.fields['diskProfile'].widget = forms.widgets.HiddenInput() + + if mem_profile_configurable and host.nodes and host.memory: + # Populate Available Memory Profile Choices + try: + avail_memory_profile_list = \ + api.sysinv.host_memprofile_list(self.request) + memory_profile_tuple_list = [ + ('', _("Copy from an available memory profile."))] + for mp in avail_memory_profile_list: + if memoryprofile_applicable(host, host._subfunctions, + mp): + memory_profile_tuple_list.append( + (mp.profilename, mp.profilename)) + + except Exception: + exceptions.handle(self.request, _( + 'Unable to retrieve list of memory profiles.')) + memory_profile_tuple_list = [] + + self.fields[ + 'memoryProfile'].choices = memory_profile_tuple_list + + else: + self.fields['cpuProfile'].widget = forms.widgets.HiddenInput() + self.fields[ + 'interfaceProfile'].widget = forms.widgets.HiddenInput() + self.fields['diskProfile'].widget = forms.widgets.HiddenInput() + self.fields['memoryProfile'].widget = forms.widgets.HiddenInput() + + def clean_location(self): + try: + host_id = self.cleaned_data['host_id'] + host = api.sysinv.host_get(self.request, host_id) + location = host._location + location['locn'] = self.cleaned_data.get('location') + return location + except Exception: + msg = _('Unable to get host data') + exceptions.check_message(["Connection", "refused"], msg) + raise + + def clean(self): + cleaned_data = super(UpdateHostInfoAction, self).clean() + disabled = self.fields['personality'].widget.attrs.get('disabled') + if disabled == 'disabled': + if self.system_type == constants.TS_AIO: + self._personality = 'controller' + cleaned_data['personality'] = self._personality + + if cleaned_data['personality'] == api.sysinv.PERSONALITY_STORAGE: + self._subfunctions = api.sysinv.PERSONALITY_STORAGE + cleaned_data['subfunctions'] = self._subfunctions + elif cleaned_data['personality'] == api.sysinv.PERSONALITY_CONTROLLER: + if self.system_type == constants.TS_AIO: + self._subfunctions = (api.sysinv.PERSONALITY_CONTROLLER + ',' + + api.sysinv.PERSONALITY_COMPUTE) + else: + self._subfunctions = api.sysinv.PERSONALITY_CONTROLLER + cleaned_data['subfunctions'] = self._subfunctions + elif cleaned_data['personality'] == api.sysinv.PERSONALITY_COMPUTE: + cleaned_data['pers_subtype'] = None + + return cleaned_data + + +class AddHostInfo(workflows.Step): + action_class = AddHostInfoAction + contributes = ("personality", + "subfunctions", + "hostname", + "mgmt_mac") + + +class UpdateHostInfo(workflows.Step): + action_class = UpdateHostInfoAction + contributes = ("host_id", + "personality", + "subfunctions", + "hostname", + "location", + "cpuProfile", + "interfaceProfile", + "diskProfile", + "memoryProfile", + "ttys_dcd", + "pers_subtype") + + +class UpdateInstallParamsAction(workflows.Action): + INSTALL_OUTPUT_CHOICES = ( + (api.sysinv.INSTALL_OUTPUT_TEXT, _("text")), + (api.sysinv.INSTALL_OUTPUT_GRAPHICAL, _("graphical")), + ) + + boot_device = forms.RegexField(label=_("Boot Device"), + max_length=255, + regex=r'^[^/\s]|(/dev/disk/by-path/(.+))', + error_messages={ + 'invalid': + _('Device path is relative to /dev')}, + help_text=_("Device for boot partition.")) + + rootfs_device = forms.RegexField(label=_("Rootfs Device"), + max_length=255, + regex=r'^[^/\s]|(/dev/disk/by-path/(.+))', + error_messages={ + 'invalid': + _('Device path is relative to /dev')}, + help_text=_("Device for rootfs " + "partition.")) + + install_output = forms.ChoiceField(label=_("Installation Output"), + choices=INSTALL_OUTPUT_CHOICES, + widget=forms.Select( + attrs={'class': 'switchable', + 'data-slug': 'install_output' + })) + + console = forms.CharField(label=_("Console"), + required=False, + help_text=_("Console configuration " + "(eg. 'ttyS0,115200' or " + "empty for none).")) + + class Meta(object): + name = _("Installation Parameters") + help_text = _( + "From here you can update the installation parameters of" + " the current host.") + + def __init__(self, request, *args, **kwargs): + super(UpdateInstallParamsAction, self).__init__(request, *args, + **kwargs) + + host_id = self.initial['host_id'] + host = api.sysinv.host_get(self.request, host_id) + + self.fields['boot_device'].initial = host.boot_device + self.fields['rootfs_device'].initial = host.rootfs_device + self.fields['install_output'].initial = host.install_output + self.fields['console'].initial = host.console + + def clean(self): + cleaned_data = super(UpdateInstallParamsAction, self).clean() + return cleaned_data + + +class UpdateInstallParams(workflows.Step): + action_class = UpdateInstallParamsAction + contributes = ("boot_device", + "rootfs_device", + "install_output", + "console") + + +class BoardManagementAction(workflows.Action): + + FIELD_LABEL_BM_IP = _("Board Management Controller IP Address") + FIELD_LABEL_BM_USERNAME = _("Board Management Controller User Name") + FIELD_LABEL_BM_PASSWORD = _("Board Management Controller Password") + FIELD_LABEL_BM_CONFIRM_PASSWORD = _("Confirm Password") + + bm_type = forms.ChoiceField( + label=_("Board Management Controller Type "), + choices=BM_TYPES_CHOICES, + required=False, + widget=forms.Select(attrs={ + 'class': 'switchable', + 'data-slug': 'bm_type'})) + + bm_ip = forms.IPField( + label=FIELD_LABEL_BM_IP, + required=False, + help_text=_( + "IP address of the Board Management Controller" + " (e.g. 172.25.0.0)"), + version=forms.IPv4 | forms.IPv6, + mask=False, + widget=forms.TextInput(attrs={ + 'class': 'switched', + 'data-switch-on': 'bm_type', + 'data-bm_type-' + + api.sysinv.BM_TYPE_GENERIC: FIELD_LABEL_BM_IP})) + + bm_username = forms.CharField( + label=FIELD_LABEL_BM_USERNAME, + required=False, + widget=forms.TextInput(attrs={ + 'autocomplete': 'off', + 'class': 'switched', + 'data-switch-on': 'bm_type', + 'data-bm_type-' + + api.sysinv.BM_TYPE_GENERIC: FIELD_LABEL_BM_USERNAME})) + + bm_password = forms.RegexField( + label=FIELD_LABEL_BM_PASSWORD, + widget=forms.PasswordInput( + render_value=False, + attrs={ + 'autocomplete': 'off', + 'class': 'switched', + 'data-switch-on': 'bm_type', + 'data-bm_type-' + + api.sysinv.BM_TYPE_GENERIC: FIELD_LABEL_BM_PASSWORD}), + regex=validators.password_validator(), + required=False, + error_messages={'invalid': validators.password_validator_msg()}) + + bm_confirm_password = forms.CharField( + label=FIELD_LABEL_BM_CONFIRM_PASSWORD, + widget=forms.PasswordInput( + render_value=False, + attrs={ + 'autocomplete': 'off', + 'class': 'switched', + 'data-switch-on': 'bm_type', + 'data-bm_type-' + + api.sysinv.BM_TYPE_GENERIC: FIELD_LABEL_BM_CONFIRM_PASSWORD}), + required=False) + + def clean(self): + cleaned_data = super(BoardManagementAction, self).clean() + + if cleaned_data.get('bm_type'): + if 'bm_ip' not in cleaned_data or not cleaned_data['bm_ip']: + raise forms.ValidationError( + _('Board management IP address is required.')) + raise forms.ValidationError( + _('Board management MAC address is required.')) + + if 'bm_username' not in cleaned_data or not \ + cleaned_data['bm_username']: + raise forms.ValidationError( + _('Board management user name is required.')) + + if 'bm_password' in cleaned_data: + if cleaned_data['bm_password'] != cleaned_data.get( + 'bm_confirm_password', None): + raise forms.ValidationError( + _('Board management passwords do not match.')) + else: + cleaned_data.pop('bm_ip') + cleaned_data.pop('bm_username') + + return cleaned_data + + # We have to protect the entire "data" dict because it contains the + # password and confirm_password strings. + @sensitive_variables('data') + def handle(self, request, data): + # Throw away the password confirmation, we're done with it. + data.pop('bm_confirm_password', None) + + +class AddBoardManagementAction(BoardManagementAction): + + class Meta(object): + name = _("Board Management") + help_text = _( + "From here you can add the" + " configuration for the board management controller.") + + +class UpdateBoardManagementAction(BoardManagementAction): + + class Meta(object): + name = _("Board Management") + help_text = _( + "From here you can update the" + " configuration of the board management controller.") + + +class AddBoardManagement(workflows.Step): + action_class = AddBoardManagementAction + contributes = ("bm_type", + "bm_ip", + "bm_username", + "bm_password", + "bm_confirm_password") + + +class UpdateBoardManagement(workflows.Step): + action_class = UpdateBoardManagementAction + contributes = ("bm_type", + "bm_ip", + "bm_username", + "bm_password", + "bm_confirm_password") + + +class AddHost(workflows.Workflow): + slug = "add" + name = _("Add Host") + finalize_button_name = _("Add Host") + success_message = _('Added host "%s".') + failure_message = _('Unable to add host "%s".') + default_steps = (AddHostInfo, + AddBoardManagement) + + success_url = 'horizon:admin:inventory:index' + failure_url = 'horizon:admin:inventory:index' + + hostname = None + + def format_status_message(self, message): + name = self.hostname + return message % name + + def handle(self, request, data): + self.hostname = data['hostname'] + self.mgmt_mac = data['mgmt_mac'] + + try: + host = api.sysinv.host_create(request, **data) + return True if host else False + + except exc.ClientException as ce: + # Display REST API error on the GUI + LOG.error(ce) + msg = self.failure_message + " " + str(ce) + self.failure_message = msg + return False + except Exception as e: + msg = self.format_status_message(self.failure_message) + str(e) + exceptions.handle(request, msg) + return False + + +class UpdateHost(workflows.Workflow): + slug = "update" + name = _("Edit Host") + finalize_button_name = _("Save") + success_message = _('Updated host "%s".') + failure_message = _('Unable to modify host "%s".') + default_steps = (UpdateHostInfo, + UpdateInstallParams, + UpdateBoardManagement) + + success_url = 'horizon:admin:inventory:index' + failure_url = 'horizon:admin:inventory:index' + + hostname = None + + def format_status_message(self, message): + name = self.hostname or self.context.get('host_id') + return message % name + + def handle(self, request, data): + self.hostname = data['hostname'] + + try: + host = api.sysinv.host_get(request, data['host_id']) + + if data['cpuProfile']: + profile_uuid = profile_get_uuid(request, data['cpuProfile']) + api.sysinv.host_apply_profile(request, data['host_id'], + profile_uuid) + data.pop('cpuProfile') + + if data['interfaceProfile']: + profile_uuid = profile_get_uuid(request, + data['interfaceProfile']) + api.sysinv.host_apply_profile(request, data['host_id'], + profile_uuid) + data.pop('interfaceProfile') + + if not data['bm_password']: + data.pop('bm_password') + + if data['diskProfile']: + profile_uuid = profile_get_uuid(request, data['diskProfile']) + api.sysinv.host_apply_profile(request, data['host_id'], + profile_uuid) + data.pop('diskProfile') + + if data['memoryProfile']: + profile_uuid = profile_get_uuid(request, data['memoryProfile']) + api.sysinv.host_apply_profile(request, data['host_id'], + profile_uuid) + data.pop('memoryProfile') + + # if not trying to change personality, skip check + if host._personality == data['personality']: + data.pop('personality') + + if data.get('rootfs_device') == host.rootfs_device: + data.pop('rootfs_device') + + if data.get('install_output') == host.install_output: + data.pop('install_output') + + if data.get('console') == host.console: + data.pop('console') + + if 'pers_subtype' in data: + if not hasattr(data, 'capabilities'): + data['capabilities'] = {} + pers_subtype = data.pop('pers_subtype') + if pers_subtype: + data['capabilities']['pers_subtype'] = pers_subtype + + # subfunctions cannot be modified once host is configured + if host._subfunctions and 'subfunctions' in data: + data.pop('subfunctions') + + host = api.sysinv.host_update(request, **data) + return True if host else False + + except exc.ClientException as ce: + # Display REST API error on the GUI + LOG.error(ce) + msg = self.failure_message + " " + str(ce) + self.failure_message = msg + return False + except Exception as e: + msg = self.format_status_message(self.failure_message) + str(e) + exceptions.handle(request, msg) + return False diff --git a/cgcs_dashboard/dashboards/admin/providernets/__init__.py b/cgcs_dashboard/dashboards/admin/providernets/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/providernets/panel.py b/cgcs_dashboard/dashboards/admin/providernets/panel.py new file mode 100755 index 00000000..0424a21d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/panel.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class Providernets(horizon.Panel): + name = _("Provider Networks") + slug = 'providernets' + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(Providernets, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + + +dashboard.Admin.register(Providernets) diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/__init__.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/forms.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/forms.py new file mode 100755 index 00000000..f474c2c3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/forms.py @@ -0,0 +1,160 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ # noqa +from neutronclient.common import exceptions as neutron_exceptions + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class CreateProviderNetwork(forms.SelfHandlingForm): + name = forms.CharField(max_length=255, + label=_("Name"), + required=True) + description = forms.CharField(max_length=255, + label=_("Description"), + required=False) + type = forms.ChoiceField(label=_("Type"), + required=True) + mtu = forms.IntegerField(label=_("MTU"), + required=True, + initial=1500, + min_value=576, + max_value=9216, + help_text=( + _("Specifies the maximum MTU value of any associated tenant " + "network. Compute node data interface MTU values must be large " + "enough to support the tenant MTU plus any additional provider " + "encapsulation headers. For example, VXLAN provider MTU of " + "1500 requires a minimum data interface MTU of 1574 bytes (1600 " + "bytes is recommended."))) + vlan_transparent = forms.BooleanField( + label=_("VLAN Transparent"), + initial=False, required=False, + help_text=_("Allow tenant networks to be created that require " + "VLAN tagged packets to be transparently passed through " + "the provider network.")) + + @classmethod + def _instantiate(cls, request, *args, **kwargs): + return cls(request, *args, **kwargs) + + def __init__(self, request, *args, **kwargs): + super(CreateProviderNetwork, self).__init__(request, *args, **kwargs) + + providernet_type_choices = [('', _("Select a network type"))] + providernet_types = api.neutron.provider_network_type_list(request) + for providernet_type in providernet_types: + providernet_type_choices.append((providernet_type.type, + providernet_type.type)) + self.fields['type'].choices = providernet_type_choices + + def clean(self): + cleaned_data = super(CreateProviderNetwork, self).clean() + if len(cleaned_data['name'].lstrip()) == 0: + raise forms.ValidationError('invalid provider name') + + return cleaned_data + + def handle(self, request, data): + try: + params = {'name': data['name'], + 'type': data['type'], + 'description': data['description'], + 'mtu': data['mtu'], + 'vlan_transparent': data['vlan_transparent']} + + network = api.neutron.provider_network_create(request, **params) + msg = (_('Provider network %s was successfully created.') % + data['name']) + LOG.debug(msg) + messages.success(request, msg) + return network + except neutron_exceptions.NeutronClientException as e: + redirect = reverse('horizon:admin:providernets:index') + exceptions.handle(request, e.message, redirect=redirect) + except Exception: + redirect = reverse('horizon:admin:providernets:index') + msg = _('Failed to create provider network %s') % data['name'] + exceptions.handle(request, msg, redirect=redirect) + + +class UpdateProviderNetwork(forms.SelfHandlingForm): + name = forms.CharField(label=_("Name"), required=False, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + type = forms.CharField(label=_("Type"), required=False, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + id = forms.CharField(widget=forms.HiddenInput) + # Mutable fields + description = forms.CharField(label=_("Description"), required=False) + mtu = forms.IntegerField(label=_("MTU"), + required=True, + initial=1500, + min_value=576, + max_value=9216, + help_text=(_("Specifies the minimum interface" + " MTU required to support this" + " provider network"))) + vlan_transparent = forms.BooleanField( + label=_("VLAN Transparent"), + initial=False, required=False, + help_text=_("Allow tenant networks to be created that require " + "VLAN tagged packets to be transparently passed through " + "the provider network. " + "Changes will not impact existing networks.")) + failure_url = 'horizon:admin:providernets:index' + + def handle(self, request, data): + try: + params = {'description': data['description'], + 'mtu': data['mtu'], + 'vlan_transparent': data['vlan_transparent']} + + providernet = api.neutron.provider_network_modify( + request, data['id'], **params) + msg = (_('Provider network %s was successfully updated.') % + data['name']) + LOG.debug(msg) + messages.success(request, msg) + return providernet + except neutron_exceptions.NeutronClientException as e: + msg = _('Failed to update provider network %s') % data['name'] + LOG.info(msg) + redirect = reverse(self.failure_url) + exceptions.handle(request, e.message, redirect=redirect) + except Exception: + msg = _('Failed to update provider network %s') % data['name'] + LOG.info(msg) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/__init__.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/forms.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/forms.py new file mode 100755 index 00000000..8fa76781 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/forms.py @@ -0,0 +1,252 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2015,2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ # noqa +from neutronclient.common import exceptions as neutron_exceptions + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class CreateProviderNetworkRange(forms.SelfHandlingForm): + providernet_id = forms.CharField(widget=forms.HiddenInput()) + name = forms.CharField(max_length=255, + label=_("Name"), + required=False) + description = forms.CharField(max_length=255, + label=_("Description"), + required=False) + shared = forms.BooleanField(label=_("Shared"), + initial=False, required=False, + widget=forms.CheckboxInput(attrs={ + 'class': 'switchable', + 'data-hide-on-checked': 'true', + 'data-slug': 'is_shared'})) + + tenant_id = forms.ChoiceField(label=_("Project"), required=False, + widget=forms.Select(attrs={ + 'class': 'switched', + 'data-switch-on': 'is_shared'})) + + minimum = forms.IntegerField(label=_("Minimum"), + min_value=1) + maximum = forms.IntegerField(label=_("Maximum"), + min_value=1) + # VXLAN specific fields + mode_choices = [('dynamic', _('Multicast VXLAN')), + ('static', _('Static VXLAN'))] + mode = forms.ChoiceField(label=_("Mode"), + initial='dynamic', + required=False, + choices=mode_choices, + widget=forms.Select( + attrs={ + 'class': 'switchable', + 'data-slug': 'vxlan_mode'})) + group_help = (_("Specify the IPv4 or IPv6 multicast address for these " + "VXLAN instances")) + group = forms.CharField(max_length=255, + label=_("Multicast Group Address"), + initial="239.0.0.1", + required=False, + help_text=group_help, + widget=forms.TextInput( + attrs={ + 'class': 'switchable switched', + 'data-slug': 'vxlan_group', + 'data-switch-on': 'vxlan_mode', + 'data-vxlan_mode-dynamic': + 'Multicast Group Address'})) + port_choices = [('4789', _('IANA Assigned VXLAN UDP port (4789)')), + ('4790', _('IANA Assigned VXLAN-GPE UDP port (4790)')), + ('8472', _('Legacy VXLAN UDP port (8472)'))] + port = forms.ChoiceField(label=_("UDP Port"), + required=True, + widget=forms.RadioSelect(), + choices=port_choices) + ttl = forms.IntegerField(label=_("TTL"), + required=False, + initial=1, + min_value=1, + max_value=255, + help_text=( + _("Specify the time-to-live value for these VXLAN instances"))) + + def __init__(self, request, *args, **kwargs): + super(CreateProviderNetworkRange, self).__init__(request, *args, + **kwargs) + + tenant_choices = [('', _("Select a project"))] + tenants, has_more = api.keystone.tenant_list(request) + for tenant in tenants: + if tenant.enabled: + tenant_choices.append((tenant.id, tenant.name)) + self.fields['tenant_id'].choices = tenant_choices + initial = kwargs['initial'] + if 'providernet_type' in initial: + providernet_type = initial['providernet_type'] + if providernet_type != "vxlan": + del self.fields["mode"] + del self.fields["group"] + del self.fields["port"] + del self.fields["ttl"] + + def handle(self, request, data): + try: + params = {'providernet_id': data['providernet_id'], + 'name': data['name'], + 'description': data['description'], + 'minimum': data['minimum'], + 'maximum': data['maximum'], + 'shared': data['shared'], + 'tenant_id': data['tenant_id']} + + if not data['tenant_id']: + params['shared'] = True + + if self.initial['providernet_type'] == "vxlan": + params['mode'] = data['mode'] + if params['mode'] == 'dynamic': + params['group'] = data['group'] + params['port'] = int(data['port']) + params['ttl'] = int(data['ttl']) + + providernet_range = api.neutron.provider_network_range_create( + request, **params) + msg = (_('Provider network range %s was successfully created.') % + providernet_range['id']) + LOG.debug(msg) + messages.success(request, msg) + return providernet_range + except neutron_exceptions.NeutronClientException as e: + LOG.info(e.message) + redirect = reverse('horizon:admin:providernets:providernets:' + 'detail', + args=(data['providernet_id'],)) + exceptions.handle(request, e.message, redirect=redirect) + except Exception: + msg = _('Failed to create a provider' + ' network range for network %s') \ + % data['providernet_id'] + LOG.info(msg) + redirect = reverse('horizon:admin:providernets:providernets:' + 'detail', + args=(data['providernet_id'],)) + exceptions.handle(request, msg, redirect=redirect) + + def clean(self): + cleaned_data = super(CreateProviderNetworkRange, self).clean() + if not cleaned_data["shared"] and not cleaned_data["tenant_id"]: + msg = "Project must be specified for non-shared Segmentation Range" + raise forms.ValidationError(msg) + if cleaned_data["shared"]: + cleaned_data["tenant_id"] = "" + + +class UpdateProviderNetworkRange(forms.SelfHandlingForm): + failure_url = 'horizon:admin:providernets:providernets:detail' + providernet_id = forms.CharField(widget=forms.HiddenInput()) + providernet_range_id = forms.CharField(widget=forms.HiddenInput()) + name = forms.CharField(max_length=255, + label=_("Name"), + required=False, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + description = forms.CharField(max_length=255, + label=_("Description"), + required=False) + minimum = forms.IntegerField(label=_("Minimum"), min_value=1) + maximum = forms.IntegerField(label=_("Maximum"), min_value=1) + shared = forms.BooleanField(widget=forms.HiddenInput(), required=False) + tenant_id = forms.CharField(widget=forms.HiddenInput(), required=False) + + # VXLAN specific fields + mode_widget = forms.TextInput(attrs={'readonly': 'readonly'}) + mode = forms.CharField(label=_("mode"), + required=False, + widget=mode_widget) + group_widget = forms.TextInput(attrs={'readonly': 'readonly'}) + group = forms.CharField(max_length=255, + label=_("Multicast Group Address"), + required=False, + widget=group_widget) + port_widget = forms.RadioSelect(attrs={'disabled': 'disabled'}) + port_choices = [('4789', _('IANA Assigned VXLAN UDP port (4789)')), + ('4790', _('IANA Assigned VXLAN-GPE UDP port (4790)')), + ('8472', _('Legacy VXLAN UDP port (8472)'))] + port = forms.ChoiceField(label=_("UDP Port"), + required=False, + widget=port_widget, + choices=port_choices) + ttl_widget = forms.TextInput(attrs={'readonly': 'readonly'}) + ttl = forms.IntegerField(label=_("TTL"), + required=False, + widget=ttl_widget) + + def __init__(self, request, *args, **kwargs): + super(UpdateProviderNetworkRange, self).__init__( + request, *args, **kwargs) + initial = kwargs['initial'] + if 'mode' not in initial: + del self.fields["mode"] + if 'group' not in initial or initial.get('mode') == 'static': + del self.fields["group"] + if 'port' not in initial: + del self.fields["port"] + if 'ttl' not in initial: + del self.fields["ttl"] + + def handle(self, request, data): + try: + params = {'description': data['description'], + 'minimum': data['minimum'], + 'maximum': data['maximum']} + + providernet_range = api.neutron.provider_network_range_modify( + request, data['providernet_range_id'], **params) + msg = (_('Provider network range %s was successfully updated.') % + data['providernet_range_id']) + LOG.debug(msg) + messages.success(request, msg) + return providernet_range + except neutron_exceptions.NeutronClientException as e: + LOG.info(e.message) + redirect = reverse('horizon:admin:providernets:providernets:' + 'detail', + args=(data['providernet_id'],)) + exceptions.handle(request, e.message, redirect=redirect) + except Exception: + msg = (_('Failed to update provider network range %s') % + data['providernet_range_id']) + LOG.info(msg) + redirect = reverse(self.failure_url, + args=[data['providernet_id']]) + exceptions.handle(request, msg, redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tables.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tables.py new file mode 100755 index 00000000..5accf0c2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tables.py @@ -0,0 +1,131 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014,2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.template import defaultfilters as filters +from django.utils.translation import ugettext_lazy as _ # noqa +from django.utils.translation import ungettext_lazy +from neutronclient.common import exceptions as neutron_exceptions + +from horizon import exceptions +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class DeleteProviderNetworkRange(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Range", + u"Delete Ranges", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Range", + u"Deleted Ranges", + count + ) + + def get_redirect_url(self): + providernet_id = self.table.kwargs['providernet_id'] + return reverse('horizon:admin:providernets:providernets:detail', + args=(providernet_id,)) + + def delete(self, request, obj_id): + try: + api.neutron.provider_network_range_delete(request, obj_id) + except neutron_exceptions.NeutronClientException as e: + LOG.info(e.message) + exceptions.handle(request, e.message, + redirect=self.get_redirect_url()) + except Exception: + msg = _('Failed to delete provider network range %s') % obj_id + LOG.info(msg) + exceptions.handle(request, msg, redirect=self.get_redirect_url()) + + +class CreateProviderNetworkRange(tables.LinkAction): + name = "create" + verbose_name = _("Create Range") + url = "horizon:admin:providernets:providernets:createrange" + classes = ("ajax-modal", "btn-create") + + def get_link_url(self, datum=None): + providernet_id = self.table.kwargs['providernet_id'] + return reverse(self.url, args=(providernet_id,)) + + def allowed(self, request, datum=None): + providernet = self.table.kwargs.get('providernet') + if providernet: + return (providernet.type not in ('flat')) + return True + + +class EditProviderNetworkRange(tables.LinkAction): + name = "update" + verbose_name = _("Edit Range") + url = "horizon:admin:providernets:providernets:editrange" + classes = ("ajax-modal", "btn-edit") + + def get_link_url(self, providernet_range): + providernet_id = self.table.kwargs['providernet_id'] + return reverse(self.url, args=(providernet_id, providernet_range.id)) + + +def _get_vxlan_provider_attributes(datum): + vxlan = datum.vxlan + return (_("Mode: {} Group: {}, Port: {}, TTL: {}").format( + vxlan['mode'], vxlan['group'], vxlan['port'], vxlan['ttl'])) + + +def _get_provider_attributes(datum): + if hasattr(datum, "vxlan"): + return _get_vxlan_provider_attributes(datum) + return _("n/a") + + +class ProviderNetworkRangeTable(tables.DataTable): + tenant = tables.Column("tenant_name", verbose_name=_("Project")) + shared = tables.Column("shared", verbose_name=_("Shared"), + filters=(filters.yesno, filters.capfirst)) + url = "horizon:admin:providernets:providernets:ranges:detail" + name = tables.Column("name", verbose_name=_("Name"), link=url) + minimum = tables.Column("minimum", verbose_name=_("Minimum")) + maximum = tables.Column("maximum", verbose_name=_("Maximum")) + attributes = tables.Column(_get_provider_attributes, + verbose_name=_("Provider Attributes")) + + class Meta(object): + name = "provider_network_ranges" + verbose_name = _("Segmentation Ranges") + table_actions = (CreateProviderNetworkRange, + DeleteProviderNetworkRange) + row_actions = (EditProviderNetworkRange, DeleteProviderNetworkRange) diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tabs.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tabs.py new file mode 100755 index 00000000..d7f0a289 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/tabs.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import tabs + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = "admin/providernets/providernets/ranges/" \ + "_detail_overview.html" + + def get_context_data(self, request): + providernet_range = self.tab_group.kwargs['providernet_range'] + return {'providernet_range': providernet_range} + + +class ProviderNetworkRangeDetailTabs(tabs.TabGroup): + slug = "providernet_range_details" + tabs = (OverviewTab,) diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/urls.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/urls.py new file mode 100755 index 00000000..6882f1b1 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/urls.py @@ -0,0 +1,34 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import url # noqa + +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import views + +RANGES = r'^(?P[^/]+)/%s$' + +urlpatterns = [ + url(RANGES % 'detail', views.DetailView.as_view(), + name='detail')] diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/views.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/views.py new file mode 100755 index 00000000..8b8a824f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/ranges/views.py @@ -0,0 +1,183 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from horizon import tabs +from horizon.utils import memoized +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import forms as range_forms +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import tabs as range_tabs + + +class CreateView(forms.ModalFormView): + form_class = range_forms.CreateProviderNetworkRange + template_name = 'admin/providernets/providernets/ranges/create.html' + success_url = 'horizon:admin:providernets:providernets:detail' + failure_url = 'horizon:admin:providernets:providernets:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['providernet_id'],)) + + def get_failure_url(self): + return reverse(self.failure_url, + args=(self.kwargs['providernet_id'],)) + + def get_object(self): + if not hasattr(self, "_object"): + try: + providernet_id = self.kwargs["providernet_id"] + self._object = api.neutron.provider_network_get(self.request, + providernet_id) + except Exception: + redirect = reverse(self.failure_url, + args=(self.kwargs['providernet_id'],)) + msg = _("Unable to retrieve provider network.") + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + context['providernet_id'] = self.kwargs["providernet_id"] + context['providernet'] = self.get_object() + return context + + def get_initial(self): + providernet = self.get_object() + return {"providernet_id": providernet.id, + "providernet_name": providernet.name, + "providernet_type": providernet.type} + + +class DetailView(tabs.TabView): + tab_group_class = range_tabs.ProviderNetworkRangeDetailTabs + template_name = 'admin/providernets/providernets/ranges/detail.html' + page_title = '{{ providernet_range.name }}' + + def _get_object(self): + if not hasattr(self, "_object"): + providernet_range_id = \ + self.kwargs['providernet_range_id'] + try: + self._object = api.neutron.provider_network_range_get( + self.request, providernet_range_id) + except Exception: + redirect = \ + reverse("horizon:admin:providernets:providernets:detail", + args=( + self.kwargs['providernet_id'],)) + msg = _('Unable to retrieve provider network range details') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + providernet_range = self._get_object() + + pnet_name = self.get_providernet_name(providernet_range.providernet_id) + breadcrumb = [ + (pnet_name, + reverse('horizon:admin:providernets:providernets:detail', + args=(providernet_range.providernet_id,))), + (_("Segmentation Ranges"), None) + ] + context["custom_breadcrumb"] = breadcrumb + + context["providernet_range"] = providernet_range + return context + + @memoized.memoized_method + def get_providernet_name(self, providernet_id): + try: + providernet = api.neutron.provider_network_get(self.request, + providernet_id) + providernet.set_id_as_name_if_empty(length=0) + except Exception: + providernet = {} + msg = _('Unable to retrieve providernet details.') + exceptions.handle(self.request, msg) + return providernet.name + + def get_tabs(self, request, *args, **kwargs): + providernet_range = self._get_object() + return self.tab_group_class( + request, providernet_range=providernet_range, **kwargs) + + +class UpdateView(forms.ModalFormView): + form_class = range_forms.UpdateProviderNetworkRange + template_name = 'admin/providernets/providernets/ranges/update.html' + context_object_name = 'providernet_range' + success_url = 'horizon:admin:providernets:providernets:detail' + + def get_success_url(self): + value = reverse(self.success_url, + args=(self.kwargs['providernet_id'],)) + return value + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + providernet_range_id = self.kwargs['providernet_range_id'] + try: + self._object = api.neutron.provider_network_range_get( + self.request, providernet_range_id) + except Exception: + redirect = \ + reverse("horizon:admin:providernets:providernets:detail", + args=(self.kwargs['providernet_id'],)) + msg = _('Unable to retrieve provider network range details') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + providernet_range = self._get_object() + context['providernet_range_id'] = providernet_range['id'] + context['providernet_id'] = providernet_range['providernet_id'] + context['providernet_range'] = providernet_range + return context + + def get_initial(self): + providernet_range = self._get_object() + data = {'providernet_id': self.kwargs['providernet_id'], + 'providernet_range_id': self.kwargs['providernet_range_id'], + 'name': providernet_range['name'], + 'description': providernet_range['description'], + 'minimum': providernet_range['minimum'], + 'maximum': providernet_range['maximum'], + 'tenant_id': providernet_range['tenant_id'], + 'shared': providernet_range['shared']} + if 'vxlan' in providernet_range: + vxlan = providernet_range['vxlan'] + data['mode'] = vxlan['mode'] + data['group'] = vxlan['group'] + data['port'] = vxlan['port'] + data['ttl'] = vxlan['ttl'] + return data diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/tables.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/tables.py new file mode 100755 index 00000000..e5677ddf --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/tables.py @@ -0,0 +1,168 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging +from operator import itemgetter # noqa + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ # noqa +from django.utils.translation import ungettext_lazy + +from neutronclient.common import exceptions as neutron_exceptions + +from horizon import exceptions +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class DeleteProviderNetwork(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Provider Network", + u"Delete Provider Networks", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Provider Network", + u"Deleted Provider Networks", + count + ) + + def delete(self, request, obj_id): + try: + api.neutron.provider_network_delete(request, obj_id) + except neutron_exceptions.NeutronClientException as e: + LOG.info(e.message) + redirect = reverse('horizon:admin:providernets:index') + exceptions.handle(request, e.message, redirect=redirect) + except Exception: + msg = _('Failed to delete provider network %s') % obj_id + LOG.info(msg) + redirect = reverse('horizon:admin:providernets:index') + exceptions.handle(request, msg, redirect=redirect) + + +class CreateProviderNetwork(tables.LinkAction): + name = "create" + verbose_name = _("Create Provider Network") + url = "horizon:admin:providernets:providernets:create" + classes = ("ajax-modal", "btn-create") + + +class EditProviderNetwork(tables.LinkAction): + name = "update" + verbose_name = _("Edit Provider Network") + url = "horizon:admin:providernets:providernets:update" + classes = ("ajax-modal", "btn-edit") + + +class AddProviderNetworkRange(tables.LinkAction): + name = "addrange" + verbose_name = _("Create Segmentation Range") + url = "horizon:admin:providernets:providernets:addrange" + classes = ("ajax-modal", "btn-edit") + + def allowed(self, request, providernet): + if providernet: + return providernet.type not in ('flat') + return super(AddProviderNetworkRange, self).allowed(request, + providernet) + + +class ProviderNetworksFilterAction(tables.FilterAction): + def filter(self, table, providernets, filter_string): + """Naive case-insensitive search.""" + q = filter_string.lower() + return [providernet for providernet in providernets + if q in providernet.name.lower()] + + +def _format_providernet_ranges(data): + ranges = data['ranges'] + if not ranges: + return '-' + return ", ".join(["{}-{}".format(r['minimum'], r['maximum']) + if r['minimum'] != r['maximum'] + else "{}".format(r['minimum']) + for r in sorted(ranges, key=itemgetter('minimum'))]) + + +class ProviderNetworksTable(tables.DataTable): + name = tables.Column("name", verbose_name=_("Network Name"), + link='horizon:admin:providernets:providernets:detail') + status = tables.Column("status", + verbose_name=_("Status"), + status=True) + type = tables.Column("type", verbose_name=_("Type")) + mtu = tables.Column("mtu", verbose_name=_("MTU")) + ranges = tables.Column(transform=_format_providernet_ranges, + verbose_name=_("Segmentation Ranges")) + vlan_transparent = tables.Column("vlan_transparent", + verbose_name=_("VLAN Transparent")) + + class Meta(object): + name = "provider_networks" + verbose_name = _("Provider Networks") + status_columns = ["status"] + table_actions = (CreateProviderNetwork, DeleteProviderNetwork, + ProviderNetworksFilterAction) + row_actions = (EditProviderNetwork, + DeleteProviderNetwork, + AddProviderNetworkRange) + + +def _get_link_url(datum): + link = 'horizon:admin:networks:detail' + return reverse(link, args=(datum.id,)) + + +def _get_segmentation_id(datum): + if datum.providernet_type.lower() == "flat": + return _("n/a") + return datum.segmentation_id + + +class ProviderNetworkTenantNetworkTable(tables.DataTable): + name = tables.Column("name", verbose_name=_("Name"), + link=_get_link_url) + vlan_id = tables.Column("vlan_id", verbose_name=_("VLAN")) + type = tables.Column("providernet_type", verbose_name=_("Type")) + segmentation_id = tables.Column(_get_segmentation_id, + verbose_name=_("Segmentation ID")) + + def get_object_id(self, datum): + # Generate a unique object id that takes in to consideration the + # providernet type, the vlan_id as well as the network id + return "{}-{}-{}".format(datum.id, datum.providernet_type, + datum.vlan_id) + + class Meta(object): + name = "tenant_networks" + verbose_name = _("Tenant Networks") diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/urls.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/urls.py new file mode 100755 index 00000000..55434714 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/urls.py @@ -0,0 +1,57 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import include # noqa +from django.conf.urls import url # noqa + +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import urls as range_urls +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import views as range_views +from openstack_dashboard.dashboards.admin.providernets.providernets import \ + views + +PROVIDERNETS = r'^(?P[^/]+)/%s$' +VIEW_MOD = 'openstack_dashboard.dashboards.admin.providernets.providernets.' \ + 'views' + +urlpatterns = [ + url(r'^create/$', views.CreateView.as_view(), + name='create'), + url(PROVIDERNETS % 'addrange', + views.CreateRangeView.as_view(), name='addrange'), + url(PROVIDERNETS % 'update', views.UpdateView.as_view(), + name='update'), + url(PROVIDERNETS % 'detail', views.DetailView.as_view(), + name='detail')] +urlpatterns += [ + url(PROVIDERNETS % 'ranges/create', + range_views.CreateView.as_view(), + name='createrange'), + url( + r'^(?P[^/]+)/ranges/' + r'(?P[^/]+)/update$', + range_views.UpdateView.as_view(), name='editrange'), + url(r'^ranges/', + include(range_urls, namespace='ranges'))] diff --git a/cgcs_dashboard/dashboards/admin/providernets/providernets/views.py b/cgcs_dashboard/dashboards/admin/providernets/providernets/views.py new file mode 100755 index 00000000..6c6b86a2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/providernets/views.py @@ -0,0 +1,185 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from collections import OrderedDict +import logging + +from django.core.urlresolvers import reverse # noqa +from django.core.urlresolvers import reverse_lazy # noqa +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from horizon import tables +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.providernets.providernets import \ + forms as providernet_forms +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import tables as range_tables +from openstack_dashboard.dashboards.admin.providernets.providernets.ranges \ + import views as range_views +from openstack_dashboard.dashboards.admin.providernets.providernets import \ + tables as providernet_tables + +LOG = logging.getLogger(__name__) + + +class CreateView(forms.ModalFormView): + form_class = providernet_forms.CreateProviderNetwork + template_name = 'admin/providernets/providernets/create.html' + success_url = reverse_lazy('horizon:admin:providernets:index') + + +class DetailView(tables.MultiTableView): + table_classes = (range_tables.ProviderNetworkRangeTable, + providernet_tables.ProviderNetworkTenantNetworkTable) + template_name = 'admin/providernets/providernets/detail.html' + failure_url = reverse_lazy('horizon:admin:providernets:index') + page_title = '{{ "Provider Network Detail: "|add:providernet.name }}' + + def _get_tenant_list(self): + if not hasattr(self, "_tenants"): + try: + tenants, has_more = api.keystone.tenant_list(self.request) + except Exception: + tenants = [] + msg = _('Unable to retrieve instance project information.') + exceptions.handle(self.request, msg) + tenant_dict = OrderedDict([(t.id, t) for t in tenants]) + self._tenants = tenant_dict + return self._tenants + + def get_tenant_networks_data(self): + try: + providernet_id = self.kwargs['providernet_id'] + # self.table.kwargs['providernet'] = self._get_data() + networks = api.neutron.provider_network_list_tenant_networks( + self.request, providernet_id=providernet_id) + except Exception: + networks = [] + msg = _('Tenant network list can not be retrieved.') + exceptions.handle(self.request, msg) + return networks + + def get_provider_network_ranges_data(self): + try: + providernet_id = self.kwargs['providernet_id'] + # self.table.kwargs['providernet'] = self._get_data() + ranges = api.neutron.provider_network_range_list( + self.request, providernet_id=providernet_id) + except Exception: + ranges = [] + msg = _('Segmentation id range list can not be retrieved.') + exceptions.handle(self.request, msg) + tenant_dict = self._get_tenant_list() + for r in ranges: + r.set_id_as_name_if_empty() + # Set tenant name + tenant = tenant_dict.get(r.tenant_id, None) + r.tenant_name = getattr(tenant, 'name', None) + return ranges + + def _get_data(self): + if not hasattr(self, "_providernet"): + try: + providernet_id = self.kwargs['providernet_id'] + providernet = api.neutron.provider_network_get( + self.request, providernet_id) + providernet.set_id_as_name_if_empty(length=0) + except Exception: + redirect = self.failure_url + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'provider network "%s".') % providernet_id, + redirect=redirect) + self._providernet = providernet + return self._providernet + + def _get_nova_data(self): + if not hasattr(self, "_providernet_nova"): + try: + providernet_id = self.kwargs['providernet_id'] + providernet_nova = api.nova.provider_network_get( + self.request, providernet_id) + except Exception as ex: + # redirect = self.failure_url + # exceptions.handle(self.request, + # _('Unable to retrieve details for ' + # 'provider network "%s".') % providernet_id, + # redirect=redirect) + LOG.error(ex) + providernet_nova = None + + self._providernet_nova = providernet_nova + return self._providernet_nova + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + context["providernet"] = self._get_data() + context["nova_providernet"] = self._get_nova_data() + return context + + +class UpdateView(forms.ModalFormView): + form_class = providernet_forms.UpdateProviderNetwork + template_name = 'admin/providernets/providernets/update.html' + success_url = reverse_lazy('horizon:admin:providernets:index') + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + context["providernet_id"] = self.kwargs['providernet_id'] + return context + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + providernet_id = self.kwargs['providernet_id'] + try: + self._object = api.neutron.provider_network_get( + self.request, providernet_id) + except Exception: + redirect = self.success_url + msg = _('Unable to retrieve provider network details.') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_initial(self): + providernet = self._get_object() + return {'id': providernet['id'], + 'name': providernet['name'], + 'description': providernet['description'], + 'type': providernet['type'], + 'mtu': providernet['mtu'], + 'vlan_transparent': providernet['vlan_transparent']} + + +class CreateRangeView(range_views.CreateView): + template_name = 'admin/providernets/providernets/add_range.html' + success_url = 'horizon:admin:providernets:index' + failure_url = 'horizon:admin:providernets:index' + + def get_success_url(self): + return reverse(self.success_url) + + def get_failure_url(self): + return reverse(self.failure_url) diff --git a/cgcs_dashboard/dashboards/admin/providernets/tabs.py b/cgcs_dashboard/dashboards/admin/providernets/tabs.py new file mode 100755 index 00000000..0a508581 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/tabs.py @@ -0,0 +1,48 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import tabs +from openstack_dashboard.api import base +from openstack_dashboard.api import neutron +from openstack_dashboard.dashboards.admin.providernets.providernets import \ + tables as providernets_tables + +LOG = logging.getLogger(__name__) + + +class ProviderNetworkTab(tabs.TableTab): + table_classes = (providernets_tables.ProviderNetworksTable,) + name = _("Provider Networks") + slug = "provider_networks" + template_name = ("horizon/common/_detail_table.html") + + def get_provider_networks_data(self): + try: + providernets = \ + neutron.provider_network_list(self.tab_group.request) + except Exception: + providernets = [] + msg = _('Unable to get provider network list.') + exceptions.check_message(["Connection", "refused"], msg) + raise + return providernets + + def allowed(self, request): + return base.is_TiS_region(request) + + +class NetworkTabs(tabs.TabGroup): + slug = "providernets" + tabs = (ProviderNetworkTab,) + sticky = True diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_add_range.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_add_range.html new file mode 100755 index 00000000..9979769d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_add_range.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}add_provider_network_range_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:providernets:providernets:addrange' providernet_id %} +{% endblock %} + +{% block modal-header %}{% trans "Create Segmentation Range" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create a segmentation range for the provider network. The range must not overlap with any other segmentation range."%}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_create.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_create.html new file mode 100755 index 00000000..85ef9cb3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_create.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_provider_network_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:providernets:providernets:create' %} +{% endblock %} + +{% block modal-header %}{% trans "Create Provider Network" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create a provider network and later segment this network for access by one or more tenant networks."%}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_detail_overview.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_detail_overview.html new file mode 100755 index 00000000..afb9c19f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_detail_overview.html @@ -0,0 +1,30 @@ +{% load i18n sizeformat %} + +

    {% trans "Provider Network Overview" %}

    + +
    +
    +
    {% trans "Name" %}
    +
    {{ providernet.name|default:_("None") }}
    +
    {% trans "ID" %}
    +
    {{ providernet.id|default:_("None") }}
    +
    {% trans "Type" %}
    +
    {{ providernet.type|default:_("None") }}
    +
    {% trans "MTU" %}
    +
    {{ providernet.mtu|default:_("-") }}
    +
    {% trans "Description" %}
    +
    {{ providernet.description|default:_("None") }}
    +
    {% trans "VLAN Transparent" %}
    +
    {{ providernet.vlan_transparent|yesno|capfirst }}
    + {% if nova_providernet %} +
    {% trans "PCI PFs Configured" %}
    +
    {{ nova_providernet.pci_pfs_configured|default:_("0") }}
    +
    {% trans "PCI PFs Used" %}
    +
    {{ nova_providernet.pci_pfs_used|default:_("0") }}
    +
    {% trans "PCI VFs Configured" %}
    +
    {{ nova_providernet.pci_vfs_configured|default:_("0") }}
    +
    {% trans "PCI VFs Used" %}
    +
    {{ nova_providernet.pci_vfs_used|default:_("0") }}
    + {% endif %} +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_update.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_update.html new file mode 100755 index 00000000..a8845268 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/_update.html @@ -0,0 +1,29 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_provider_network_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:providernets:providernets:update' providernet_id %}{% endblock %} + +{% block modal-header %}{% trans "Edit Provider Network" %}{% endblock %} + +{% block modal-body %} +
    +
    +
    {% trans "ID" %}
    +
    {{ providernet_id }}
    +
    +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    {% trans "You may update the editable properties of your provider network here." %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/add_range.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/add_range.html new file mode 100755 index 00000000..6c7dca86 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/add_range.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Segmentation Range" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Segmentation Range") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/providernets/providernets/_add_range.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/create.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/create.html new file mode 100755 index 00000000..0dd55776 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Provider Network" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Provider Network") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/providernets/providernets/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/detail.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/detail.html new file mode 100755 index 00000000..f932ea77 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/detail.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Provider Network Detail"%}{% endblock %} + +{% block main %} + {% include "admin/providernets/providernets/_detail_overview.html" %} +
    +
    + {{ provider_network_ranges_table.render }} +
    +
    + {{ tenant_networks_table.render }} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_create.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_create.html new file mode 100755 index 00000000..bc5b7c36 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_create.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_provider_network_range_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:providernets:providernets:createrange' providernet_id %} +{% endblock %} + +{% block modal-header %}{% trans "Create Segmentation Range" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create a segmentation range for the provider network. The range must not overlap with any other segmentation range."%}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_detail_overview.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_detail_overview.html new file mode 100755 index 00000000..910cc19f --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_detail_overview.html @@ -0,0 +1,24 @@ +{% load i18n sizeformat %} + +
    +

    {% trans "Segmentation Range" %}

    +
    +
    +
    {% trans "Name" %}
    +
    {{ providernet_range.name|default:_("None") }}
    +
    {% trans "Description" %}
    +
    {{ providernet_range.description|default:_("None") }}
    +
    {% trans "Project ID" %}
    +
    {{ providernet_range.tenant_id|default:"-" }}
    +
    {% trans "Shared" %}
    +
    {{ providernet_range.shared|yesno|capfirst }}
    +
    {% trans "Minimum" %}
    +
    {{ providernet_range.minimum|default:_("None") }}
    +
    {% trans "Maximum" %}
    +
    {{ providernet_range.maximum|default:_("None") }}
    + {% if providernet_range.vxlan %} +
    {% trans "Provider Attributes" %}
    +
    {% include "admin/providernets/providernets/ranges/_vxlan.html" with vxlan=providernet_range.vxlan %}
    + {% endif %} +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_update.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_update.html new file mode 100755 index 00000000..9c5833b5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_update.html @@ -0,0 +1,29 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_provider_network_range_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:providernets:providernets:editrange' providernet_id providernet_range_id %}{% endblock %} + +{% block modal-header %}{% trans "Edit Segmentation Range" %}{% endblock %} + +{% block modal-body %} +
    +
    +
    {% trans "ID" %}
    +
    {{ providernet_range_id }}
    +
    +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    {% trans "You may update the editable properties of your segmentation range here." %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_vxlan.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_vxlan.html new file mode 100644 index 00000000..0a8fc379 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/_vxlan.html @@ -0,0 +1,3 @@ +{% for name, value in vxlan.items %} +
  • {{ name | capfirst }}: {{ value }}
  • +{% endfor %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/create.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/create.html new file mode 100755 index 00000000..a5e52173 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Segmentation Range" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Segmentation Range") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/providernets/providernets/ranges/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/detail.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/detail.html new file mode 100755 index 00000000..de77f9f0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Segmentation Range Detail"%}{% endblock %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/update.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/update.html new file mode 100755 index 00000000..152f3f04 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/ranges/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Provider Network Range" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Provider Network Range") %} +{% endblock page_header %} + +{% block main %} + {% include 'admin/providernets/providernets/ranges/_update.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/update.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/update.html new file mode 100755 index 00000000..605e3d85 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/providernets/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Provider Network" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Provider Network") %} +{% endblock page_header %} + +{% block main %} + {% include 'admin/providernets/providernets/_update.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/tabs.html b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/tabs.html new file mode 100755 index 00000000..c8fd1bd0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/templates/providernets/tabs.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Provider Networks" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Provider Networks")%} +{% endblock page_header %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/providernets/urls.py b/cgcs_dashboard/dashboards/admin/providernets/urls.py new file mode 100755 index 00000000..afadd4c0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/urls.py @@ -0,0 +1,23 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.conf.urls import include +from django.conf.urls import url + +from openstack_dashboard.dashboards.admin.providernets.providernets \ + import urls as providernet_urls +from openstack_dashboard.dashboards.admin.providernets import views + + +NETWORKS = r'^(?P[^/]+)/%s$' + +urlpatterns = [ + url(r'^$', views.IndexViewTabbed.as_view(), name='index'), + url(r'^providernets/', + include(providernet_urls, namespace='providernets')) +] diff --git a/cgcs_dashboard/dashboards/admin/providernets/views.py b/cgcs_dashboard/dashboards/admin/providernets/views.py new file mode 100755 index 00000000..e75bfdf2 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/providernets/views.py @@ -0,0 +1,20 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.utils.translation import ugettext_lazy as _ + +from horizon import tabs + +from openstack_dashboard.dashboards.admin.providernets \ + import tabs as project_tabs + + +class IndexViewTabbed(tabs.TabbedTableView): + tab_group_class = project_tabs.NetworkTabs + template_name = 'admin/providernets/tabs.html' + page_title = _("Provider Networks") diff --git a/cgcs_dashboard/dashboards/admin/server_groups/__init__.py b/cgcs_dashboard/dashboards/admin/server_groups/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/server_groups/forms.py b/cgcs_dashboard/dashboards/admin/server_groups/forms.py new file mode 100755 index 00000000..1d99fd02 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/forms.py @@ -0,0 +1,200 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# All rights reserved. + +""" +Views for managing volumes. +""" + +from django.conf import settings +from django.core.urlresolvers import reverse +from django.forms import ValidationError +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard import api +from openstack_dashboard.api import cinder +from openstack_dashboard.api import nova +from openstack_dashboard.dashboards.project.instances import tables + + +class CreateForm(forms.SelfHandlingForm): + tenantP = forms.ChoiceField(label=_("Project"), required=True) + name = forms.CharField(max_length="255", label=_("Server Group Name")) + policy = forms.ChoiceField(label=_("Policy"), + required=False, + widget=forms.Select( + attrs={ + 'class': 'switchable', + 'data-slug': 'policy_ht'})) + + is_best_effort = forms.BooleanField(label=_("Best Effort"), required=False) + + group_size = forms.IntegerField( + min_value=1, + label=_("Max Group Size (Instances)"), + required=False, + widget=forms.TextInput( + attrs={ + 'class': 'switchable switched', + 'data-switch-on': 'policy_ht', + 'data-policy_ht-anti-affinity': 'Max Group Size (Instances)', + 'data-policy_ht-affinity': 'Max Group Size (Instances)'})) + + group_size_ht = forms.IntegerField( + label=_("Max Group Size (Instances)"), + required=False, + widget=forms.TextInput( + attrs={ + 'readonly': 'readonly', + 'class': 'switchable switched', + 'data-switch-on': 'policy_ht', + 'data-policy_ht-affinity-hyperthread': + 'Max Group Size (Instances)'})) + + def __init__(self, request, *args, **kwargs): + super(CreateForm, self).__init__(request, *args, **kwargs) + self.fields['policy'].choices = [("anti-affinity", "anti-affinity"), + ("affinity", "affinity")] + + # Populate available project_id/name choices + all_projects = [] + try: + # Get list of available projects. + all_projects, has_more = api.keystone.tenant_list(request) + + projects_list = [(project.id, project.name) + for project in all_projects] + + except Exception: + projects_list = [] + exceptions.handle(self.request, + _('Unable to retrieve list of tenants.')) + + self.fields['tenantP'].choices = projects_list + + def handle(self, request, data): + try: + policy = data['policy'] + policies = [] + if policy: + policies.append(policy) + metadata = {} + if data['is_best_effort']: + metadata['wrs-sg:best_effort'] = "true" + group_size = data['group_size'] + group_size_ht = data['group_size_ht'] + if group_size: + metadata['wrs-sg:group_size'] = str(group_size) + elif group_size_ht: + metadata['wrs-sg:group_size'] = str(group_size_ht) + + kwargs = {'name': data['name'], + 'policies': policies, + 'metadata': metadata} + + server_group = nova.server_group_create(request, **kwargs) + return server_group + + except ValidationError as e: + self.api_error(e.messages[0]) + return False + except Exception: + exceptions.handle(request, ignore=True) + self.api_error(_("Unable to create server group.")) + return False + + +class AttachForm(forms.SelfHandlingForm): + instance = forms.ChoiceField(label=_("Attach to Server Group"), + help_text=_("Select an server group to " + "attach to.")) + device = forms.CharField(label=_("Device Name")) + + def __init__(self, *args, **kwargs): + super(AttachForm, self).__init__(*args, **kwargs) + + # Hide the device field if the hypervisor doesn't support it. + hypervisor_features = getattr(settings, + "OPENSTACK_HYPERVISOR_FEATURES", {}) + can_set_mount_point = hypervisor_features.get("can_set_mount_point", + True) + if not can_set_mount_point: + self.fields['device'].widget = forms.widgets.HiddenInput() + self.fields['device'].required = False + + # populate volume_id + volume = kwargs.get('initial', {}).get("volume", None) + if volume: + volume_id = volume.id + else: + volume_id = None + self.fields['volume_id'] = forms.CharField(widget=forms.HiddenInput(), + initial=volume_id) + + # Populate instance choices + instance_list = kwargs.get('initial', {}).get('instances', []) + instances = [] + for instance in instance_list: + if instance.status in tables.ACTIVE_STATES and \ + not any(instance.id == att["server_id"] + for att in volume.attachments): + instances.append((instance.id, '%s (%s)' % (instance.name, + instance.id))) + if instances: + instances.insert(0, ("", _("Select an instance"))) + else: + instances = (("", _("No instances available")),) + self.fields['instance'].choices = instances + + def handle(self, request, data): + instance_choices = dict(self.fields['instance'].choices) + instance_name = instance_choices.get(data['instance'], + _("Unknown instance (None)")) + # The name of the instance in the choices list has the ID appended to + # it, so let's slice that off... + instance_name = instance_name.rsplit(" (")[0] + try: + attach = api.nova.instance_volume_attach(request, + data['volume_id'], + data['instance'], + data.get('device', '')) + volume = cinder.volume_get(request, data['volume_id']) + if not volume.display_name: + volume_name = volume.id + else: + volume_name = volume.display_name + message = _('Attaching volume %(vol)s to instance ' + '%(inst)s on %(dev)s.') % {"vol": volume_name, + "inst": instance_name, + "dev": attach.device} + messages.info(request, message) + return True + except Exception: + redirect = reverse("horizon:project:volumes:index") + exceptions.handle(request, + _('Unable to attach volume.'), + redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/server_groups/panel.py b/cgcs_dashboard/dashboards/admin/server_groups/panel.py new file mode 100755 index 00000000..43230d9e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/panel.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ + +import horizon + + +class ServerGroups(horizon.Panel): + name = _("Server Groups") + slug = 'server_groups' + # Server groups are wrs-specific + permissions = ('openstack.services.compute',) + policy_rules = (("compute", "context_is_admin"),) diff --git a/cgcs_dashboard/dashboards/admin/server_groups/tables.py b/cgcs_dashboard/dashboards/admin/server_groups/tables.py new file mode 100755 index 00000000..04efdd41 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/tables.py @@ -0,0 +1,320 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2014 Wind River, Inc. +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.core.urlresolvers import NoReverseMatch +from django.core.urlresolvers import reverse +from django.utils import html +from django.utils import safestring +from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ # noqa +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables + +from openstack_dashboard import api +from openstack_dashboard.api import nova +from openstack_dashboard.dashboards.project.volumes.tables \ + import get_attachment_name +from openstack_dashboard.usage import quotas + + +DELETABLE_STATES = ("available", "error") + + +class DeleteServerGroup(tables.DeleteAction): + data_type_singular = _("Server Group") + data_type_plural = _("Server Groups") + action_past = _("Scheduled deletion of") + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Server Group", + u"Server Groups", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Image", + u"Deleted Images", + count + ) + + def delete(self, request, obj_id): + obj = self.table.get_object_by_id(obj_id) + name = self.table.get_object_display(obj) + + try: + nova.server_group_delete(request, obj_id) + except Exception: + msg = _('Unable to delete group "%s" because it is not empty. ' + 'Either delete the member instances' + ' or remove them from the group.') + exceptions.check_message(["group", "not", "empty."], msg % name) + raise + + # maybe do a precheck to see if the group is empty first? + def allowed(self, request, server_group=None): + return True + + +class CreateServerGroup(tables.LinkAction): + name = "create" + verbose_name = _("Create Server Group") + url = "horizon:admin:server_groups:create" + classes = ("ajax-modal", "btn-create") + icon = "plus" + + def allowed(self, request, volume=None): + usages = quotas.tenant_quota_usages(request) + if usages['server_groups']['available'] <= 0: + if "disabled" not in self.classes: + self.classes = [c for c in self.classes] + ['disabled'] + self.verbose_name = string_concat(self.verbose_name, ' ', + _("(Quota exceeded)")) + else: + self.verbose_name = _("Create Server Group") + classes = [c for c in self.classes if c != "disabled"] + self.classes = classes + return True + + +class EditAttachments(tables.LinkAction): + name = "attachments" + verbose_name = _("Edit Attachments") + url = "horizon:admin:server_groups:attach" + classes = ("ajax-modal", "btn-edit") + + def allowed(self, request, server_group=None): + return True # volume.status in ("available", "in-use") + + +class CreateSnapshot(tables.LinkAction): + name = "snapshots" + verbose_name = _("Create Snapshot") + url = "horizon:admin:server_groups:create_snapshot" + classes = ("ajax-modal", "btn-camera") + + def allowed(self, request, server_group=None): + return True # server_group.status == "available" + + +class UpdateRow(tables.Row): + ajax = True + + def get_data(self, request, server_group_id): + server_group = nova.server_group_get(request, server_group_id) + if not server_group.name: + server_group.name = server_group_id + + return server_group + + +def get_policies(server_group): + policies = ', '.join(server_group.policies) + return policies + + +def get_metadata(server_group): + metadata_items = ['{}:{}'.format(x, y) for x, y in + server_group.metadata.items()] + metadata = ', '.join(metadata_items) + return metadata + + +def get_member_name(request, server_id): + try: + server = api.nova.server_get(request, server_id) + name = server.name + except Exception: + name = None + exceptions.handle(request, _("Unable to retrieve " + "member information.")) + # try and get a URL + try: + url = reverse("horizon:admin:instances:detail", args=(server_id,)) + instance = '%s' % (url, html.escape(name)) + except NoReverseMatch: + instance = name + return instance + + +class ProjectNameColumn(tables.Column): + """Customized column class + + Customized column class that does complex processing on the + server group. + """ + + def get_raw_data(self, server_group): + request = self.table.request + project_id = getattr(server_group, 'project_id', None) + try: + tenant = api.keystone.tenant_get(request, + project_id, + admin=True) + + project_name = getattr(tenant, "name", None) + except Exception: + project_name = "(not found)" + + return project_name + + +class MemberColumn(tables.Column): + """Customized column class + + Customized column class that does complex processing on the instances + in a server group. This was substantially copied + from the volume equivalent. + """ + + def get_raw_data(self, server_group): + request = self.table.request + link = _('%(name)s (%(id)s)') + members = [] + for member in server_group.members: + member_id = member + name = get_member_name(request, member) + vals = {"name": name, "id": member_id} + members.append(link % vals) + return safestring.mark_safe(", ".join(members)) + + +def get_server_group_type(server_group): + return server_group.volume_type if server_group.volume_type != "None" \ + else None + + +class ServerGroupsFilterAction(tables.FilterAction): + def filter(self, table, server_groups, filter_string): + """Naive case-insensitive search.""" + q = filter_string.lower() + return [group for group in server_groups + if q in group.display_name.lower()] + + +class ServerGroupsTable(tables.DataTable): + projectname = ProjectNameColumn("project_name", + verbose_name=_("Project")) + name = tables.Column("name", + verbose_name=_("Group Name"), + link="horizon:admin:server_groups:detail") + policies = tables.Column(get_policies, + verbose_name=_("Policies")) + members = MemberColumn("members", + verbose_name=_("Members")) + metadata = tables.Column(get_metadata, + verbose_name=_("Metadata")) + + class Meta(object): + name = "server_groups" + verbose_name = _("Server Groups") + row_class = UpdateRow + table_actions = ( + CreateServerGroup, DeleteServerGroup, ServerGroupsFilterAction) + row_actions = (DeleteServerGroup,) + + def get_object_display(self, obj): + return obj.name + + +class DetachServerGroup(tables.BatchAction): + name = "detach" + action_present = _("Detach") + action_past = _("Detaching") # This action is asynchronous. + data_type_singular = _("Server Group") + data_type_plural = _("Server Groups") + action_type = 'danger' + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Server Group", + u"Server Groups", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Group", + u"Deleted Groups", + count + ) + + def action(self, request, obj_id): + attachment = self.table.get_object_by_id(obj_id) + api.nova.instance_server_group_detach(request, + attachment.get('server_id', + None), + obj_id) + + def get_success_url(self, request): + return reverse('horizon:admin:server_groups:index') + + +class AttachedInstanceColumn(tables.Column): + """Customized column class + + Customized column class that does complex processing on the attachments + for a server group. + """ + + def get_raw_data(self, attachment): + request = self.table.request + return safestring.mark_safe(get_attachment_name(request, attachment, + True)) + + +class AttachmentsTable(tables.DataTable): + instance = AttachedInstanceColumn(get_member_name, + verbose_name=_("Instance")) + device = tables.Column("device", + verbose_name=_("Device")) + + def get_object_id(self, obj): + return obj['id'] + + def get_object_display(self, attachment): + instance_name = get_attachment_name(self.request, attachment, True) + vals = {"dev": attachment['device'], + "instance_name": html.strip_tags(instance_name)} + return _("%(dev)s on instance %(instance_name)s") % vals + + def get_object_by_id(self, obj_id): + for obj in self.data: + if self.get_object_id(obj) == obj_id: + return obj + raise ValueError('No match found for the id "%s".' % obj_id) + + class Meta(object): + name = "attachments" + verbose_name = _("Attachments") + table_actions = (DetachServerGroup,) + row_actions = (DetachServerGroup,) diff --git a/cgcs_dashboard/dashboards/admin/server_groups/tabs.py b/cgcs_dashboard/dashboards/admin/server_groups/tabs.py new file mode 100755 index 00000000..0c51bf64 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/tabs.py @@ -0,0 +1,58 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import tabs + +from openstack_dashboard.api import nova + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = ("admin/server_groups/" + "_detail_overview.html") + + def get_context_data(self, request): + server_group_id = self.tab_group.kwargs['server_group_id'] + try: + server_group = nova.server_group_get(request, server_group_id) + server_group.members_display = [] + for member in server_group.members: + server_group.members_display.append( + dict(id=member, instance=nova.server_get(request, member))) + except Exception: + redirect = reverse('horizon:admin:server_groups:index') + exceptions.handle(self.request, + _('Unable to retrieve server group details.'), + redirect=redirect) + return {'server_group': server_group} + + +class ServerGroupDetailTabs(tabs.TabGroup): + slug = "server_group_details" + tabs = (OverviewTab,) diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_attach.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_attach.html new file mode 100755 index 00000000..462f0ee8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_attach.html @@ -0,0 +1,26 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}attach_volume_form{% endblock %} +{% block form_action %}{% url 'horizon:project:volumes:attach' volume.id %}{% endblock %} +{% block form_class %}{{ block.super }} horizontal {% if show_attach %}split_half{% else %} no_split{% endif %}{% endblock %} + +{% block modal_id %}attach_volume_modal{% endblock %} +{% block modal-header %}{% trans "Manage Volume Attachments" %}{% endblock %} + +{% block modal-body %} + {% if show_attach %} +

    {% trans "Attach To Instance" %}

    +
    + {% include "horizon/common/_form_fields.html" %} +
    + {% endif %} +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + {% if show_attach %} + + {% endif %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_create.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_create.html new file mode 100755 index 00000000..068b8315 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_create.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}{% endblock %} +{% block form_action %}{% url 'horizon:admin:server_groups:create' %}?{{ request.GET.urlencode }}{% endblock %} + +{% block modal_id %}create_server_group_modal{% endblock %} +{% block modal-header %}{% trans "Create Server Group" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    + +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can create a new server group" %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_detail_overview.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_detail_overview.html new file mode 100755 index 00000000..7b42dcee --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/_detail_overview.html @@ -0,0 +1,53 @@ +{% load i18n sizeformat parse_date %} +{% load url from future %} + +

    {% trans "Server Group Overview" %}: {{server_group.name }}

    + +
    +

    {% trans "Info" %}

    +
    +
    +
    {% trans "Name" %}
    +
    {{ server_group.name }}
    +
    {% trans "ID" %}
    +
    {{ server_group.id }}
    +
    {% trans "Status" %}
    +
    {{ server_group.status|capfirst }}
    +
    +
    + +
    +

    {% trans "Members" %}

    +
    +
    + {% for member in server_group.members_display %} +
    + {% url 'horizon:admin:instances:detail' member.id as instance_url%} + {{ member.instance.name }} ({{ member.id }}) +
    + {% empty %} +
    {% trans "No members" %}
    + {% endfor %} +
    +
    + +
    +

    {% trans "Policies" %}

    +
    +
    + {% for policy in server_group.policies %} +
    {{ policy }}
    + {% endfor %} +
    +
    + +
    +

    {% trans "Metadata" %}

    +
    +
    + {% for key, value in server_group.metadata.items %} +
    {{ key }}
    +
    {{ value }}
    + {% endfor %} +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/attach.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/attach.html new file mode 100755 index 00000000..4b4dad86 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/attach.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Manage Volume Attachments" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Manage Volume Attachments") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/volumes/_attach.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/create.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/create.html new file mode 100755 index 00000000..39b7d120 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Server Group" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create a Server Group") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/server_groups/_create.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/detail.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/detail.html new file mode 100755 index 00000000..3ce02ba3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Server Group Details" %}{% endblock %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/index.html b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/index.html new file mode 100755 index 00000000..44a03e4c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/templates/server_groups/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Server Groups" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Server Groups") %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/server_groups/urls.py b/cgcs_dashboard/dashboards/admin/server_groups/urls.py new file mode 100755 index 00000000..5df47c0a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/urls.py @@ -0,0 +1,39 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import url + +from openstack_dashboard.dashboards.admin.server_groups import views + + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^create/$', views.CreateView.as_view(), name='create'), + url(r'^(?P[^/]+)/attach/$', + views.EditAttachmentsView.as_view(), + name='attach'), + url(r'^(?P[^/]+)/$', + views.DetailView.as_view(), + name='detail') +] diff --git a/cgcs_dashboard/dashboards/admin/server_groups/views.py b/cgcs_dashboard/dashboards/admin/server_groups/views.py new file mode 100755 index 00000000..e68dcc58 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/server_groups/views.py @@ -0,0 +1,154 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +""" +Views for managing server groups. +""" + +from django.core.urlresolvers import reverse_lazy # noqa +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon import tabs + +from openstack_dashboard import api +from openstack_dashboard.usage import quotas + +from openstack_dashboard.dashboards.admin.server_groups \ + import forms as admin_forms + +from openstack_dashboard.dashboards.admin.server_groups \ + import tables as admin_tables +from openstack_dashboard.dashboards.admin.server_groups \ + import tabs as admin_tabs + + +# server groups don't currently support pagination +class IndexView(tables.DataTableView): + table_class = admin_tables.ServerGroupsTable + template_name = 'admin/server_groups/index.html' + page_title = _("Server Groups") + + def get_data(self): + try: + server_groups = api.nova.server_group_list( + self.request) + except Exception: + server_groups = [] + exceptions.handle(self.request, + _('Unable to retrieve server groups.')) + return server_groups + + +class DetailView(tabs.TabView): + tab_group_class = admin_tabs.ServerGroupDetailTabs + template_name = 'admin/server_groups/detail.html' + page_title = 'Server Group Details' + + +class CreateView(forms.ModalFormView): + form_class = admin_forms.CreateForm + template_name = 'admin/server_groups/create.html' + success_url = reverse_lazy("horizon:admin:server_groups:index") + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + try: + context['usages'] = quotas.tenant_limit_usages(self.request) + except Exception: + exceptions.handle(self.request) + return context + + +class EditAttachmentsView(tables.DataTableView, forms.ModalFormView): + table_class = admin_tables.AttachmentsTable + form_class = admin_forms.AttachForm + template_name = 'admin/server_groups/attach.html' + success_url = reverse_lazy("horizon:admin:server_groups:index") + + def get_object(self): + if not hasattr(self, "_object"): + volume_id = self.kwargs['volume_id'] + try: + self._object = api.cinder.volume_get(self.request, volume_id) + except Exception: + self._object = None + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return self._object + + def get_data(self): + try: + volumes = self.get_object() + attachments = [att for att in volumes.attachments if att] + except Exception: + attachments = [] + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return attachments + + def get_initial(self): + try: + instances, has_more = api.nova.server_list(self.request) + except Exception: + instances = [] + exceptions.handle(self.request, + _("Unable to retrieve attachment information.")) + return {'volume': self.get_object(), + 'instances': instances} + + def get_form(self): + if not hasattr(self, "_form"): + form_class = self.get_form_class() + self._form = super(EditAttachmentsView, self).get_form(form_class) + return self._form + + def get_context_data(self, **kwargs): + context = super(EditAttachmentsView, self).get_context_data(**kwargs) + context['form'] = self.get_form() + volume = self.get_object() + if volume and volume.status == 'available': + context['show_attach'] = True + else: + context['show_attach'] = False + context['volume'] = volume + if self.request.is_ajax(): + context['hide'] = True + return context + + def get(self, request, *args, **kwargs): + # Table action handling + handled = self.construct_tables() + if handled: + return handled + return self.render_to_response(self.get_context_data(**kwargs)) + + def post(self, request, *args, **kwargs): + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.get(request, *args, **kwargs) diff --git a/cgcs_dashboard/dashboards/admin/software_management/__init__.py b/cgcs_dashboard/dashboards/admin/software_management/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/software_management/forms.py b/cgcs_dashboard/dashboards/admin/software_management/forms.py new file mode 100755 index 00000000..448a43c9 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/forms.py @@ -0,0 +1,266 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UploadPatchForm(forms.SelfHandlingForm): + failure_url = 'horizon:admin:software_management:index' + patch_files = forms.FileField(label=_("Patch File(s)"), + widget=forms.FileInput(attrs={ + 'data-source-file': _('Patch File(s)'), + 'multiple': "multiple"}), + required=True) + + def __init__(self, *args, **kwargs): + super(UploadPatchForm, self).__init__(*args, **kwargs) + + def clean(self): + data = super(UploadPatchForm, self).clean() + return data + + def handle(self, request, data): + success_responses = [] + failure_responses = [] + + for f in request.FILES.getlist('patch_files'): + try: + success_responses.append( + api.patch.upload_patch(request, f, f.name)) + except Exception as ex: + failure_responses.append(str(ex)) + + # Consolidate server responses into one success/error message + # respectively + if success_responses: + if len(success_responses) == 1: + messages.success(request, success_responses[0]) + else: + success_msg = "" + for i in range(len(success_responses)): + success_msg += str(i + 1) + ") " + success_responses[i] + messages.success(request, success_msg) + + if failure_responses: + if len(failure_responses) == 1: + messages.error(request, failure_responses[0]) + else: + error_msg = "" + for i in range(len(failure_responses)): + error_msg += str(i + 1) + ") " + failure_responses[i] + messages.error(request, error_msg) + + return True + + +class CreatePatchStrategyForm(forms.SelfHandlingForm): + failure_url = 'horizon:admin:software_management:index' + + CONTROLLER_APPLY_TYPES = ( + ('serial', _("Serial")), + ('ignore', _("Ignore")), + ) + + AIO_APPLY_TYPES = ( + ('serial', _("Serial")), + ) + + GENERIC_APPLY_TYPES = ( + ('serial', _("Serial")), + ('parallel', _("Parallel")), + ('ignore', _("Ignore")), + ) + + INSTANCE_ACTIONS = ( + ('stop-start', _("Stop-Start")), + ('migrate', _("Migrate")), + ) + + SIMPLEX_INSTANCE_ACTIONS = ( + ('stop-start', _("Stop-Start")), + ) + + ALARM_RESTRICTION_TYPES = ( + ('strict', _("Strict")), + ('relaxed', _("Relaxed")), + ) + + controller_apply_type = forms.ChoiceField( + label=_("Controller Apply Type"), + required=True, + choices=CONTROLLER_APPLY_TYPES, + widget=forms.Select()) + + storage_apply_type = forms.ChoiceField( + label=_("Storage Apply Type"), + required=True, + choices=GENERIC_APPLY_TYPES, + widget=forms.Select()) + + compute_apply_type = forms.ChoiceField( + label=_("Compute Apply Type"), + required=True, + choices=GENERIC_APPLY_TYPES, + widget=forms.Select( + attrs={ + 'class': 'switchable', + 'data-slug': 'compute_apply_type'})) + + max_parallel_compute_hosts = forms.IntegerField( + label=_("Maximum Parallel Compute Hosts"), + initial=2, + min_value=2, + max_value=10, + required=True, + error_messages={'invalid': _('Maximum Parallel Compute Hosts must be ' + 'between 2 and 10.')}, + widget=forms.TextInput( + attrs={ + 'class': 'switched', + 'data-switch-on': 'compute_apply_type', + 'data-compute_apply_type-parallel': + 'Maximum Parallel Compute Hosts'})) + + default_instance_action = forms.ChoiceField( + label=_("Default Instance Action"), + required=True, + choices=INSTANCE_ACTIONS, + widget=forms.Select()) + + alarm_restrictions = forms.ChoiceField( + label=_("Alarm Restrictions"), + required=True, + choices=ALARM_RESTRICTION_TYPES, + widget=forms.Select()) + + def __init__(self, request, *args, **kwargs): + super(CreatePatchStrategyForm, self).__init__(request, *args, **kwargs) + + cinder_backend = api.sysinv.get_cinder_backend(request) + if api.sysinv.CINDER_BACKEND_CEPH not in cinder_backend: + del self.fields['storage_apply_type'] + + system_type = api.sysinv.get_system_type(request) + if system_type == api.sysinv.SYSTEM_TYPE_AIO: + del self.fields['controller_apply_type'] + self.fields['compute_apply_type'].choices = self.AIO_APPLY_TYPES + + if api.sysinv.is_system_mode_simplex(request): + self.fields['default_instance_action'].choices = \ + self.SIMPLEX_INSTANCE_ACTIONS + + def clean(self): + data = super(CreatePatchStrategyForm, self).clean() + return data + + def handle(self, request, data): + try: + response = api.vim.create_strategy( + request, api.vim.STRATEGY_SW_PATCH, + data.get('controller_apply_type', 'ignore'), + data.get('storage_apply_type', 'ignore'), 'ignore', + data['compute_apply_type'], + data['max_parallel_compute_hosts'], + data['default_instance_action'], + data['alarm_restrictions']) + if not response: + messages.error(request, "Strategy creation failed") + except Exception: + redirect = reverse(self.failure_url) + exceptions.handle(request, "Strategy creation failed", + redirect=redirect) + return True + + +class CreateUpgradeStrategyForm(forms.SelfHandlingForm): + failure_url = 'horizon:admin:software_management:index' + + GENERIC_APPLY_TYPES = ( + ('serial', _("Serial")), + ('parallel', _("Parallel")), + ('ignore', _("Ignore")), + ) + + ALARM_RESTRICTION_TYPES = ( + ('strict', _("Strict")), + ('relaxed', _("Relaxed")), + ) + + storage_apply_type = forms.ChoiceField( + label=_("Storage Apply Type"), + required=True, + choices=GENERIC_APPLY_TYPES, + widget=forms.Select()) + + compute_apply_type = forms.ChoiceField( + label=_("Compute Apply Type"), + required=True, + choices=GENERIC_APPLY_TYPES, + widget=forms.Select( + attrs={ + 'class': 'switchable', + 'data-slug': 'compute_apply_type'})) + + max_parallel_compute_hosts = forms.IntegerField( + label=_("Maximum Parallel Compute Hosts"), + initial=2, + min_value=2, + max_value=10, + required=True, + error_messages={'invalid': _('Maximum Parallel Compute Hosts must be ' + 'between 2 and 10.')}, + widget=forms.TextInput( + attrs={ + 'class': 'switched', + 'data-switch-on': 'compute_apply_type', + 'data-compute_apply_type-parallel': + 'Maximum Parallel Compute Hosts'})) + + alarm_restrictions = forms.ChoiceField( + label=_("Alarm Restrictions"), + required=True, + choices=ALARM_RESTRICTION_TYPES, + widget=forms.Select()) + + def __init__(self, request, *args, **kwargs): + super(CreateUpgradeStrategyForm, self).__init__(request, *args, + **kwargs) + cinder_backend = api.sysinv.get_cinder_backend(request) + if api.sysinv.CINDER_BACKEND_CEPH not in cinder_backend: + del self.fields['storage_apply_type'] + + def clean(self): + data = super(CreateUpgradeStrategyForm, self).clean() + return data + + def handle(self, request, data): + try: + response = api.vim.create_strategy( + request, api.vim.STRATEGY_SW_UPGRADE, 'ignore', + data.get('storage_apply_type', 'ignore'), 'ignore', + data['compute_apply_type'], + data['max_parallel_compute_hosts'], + 'migrate', + data['alarm_restrictions']) + if not response: + messages.error(request, "Strategy creation failed") + except Exception: + redirect = reverse(self.failure_url) + exceptions.handle(request, "Strategy creation failed", + redirect=redirect) + return True diff --git a/cgcs_dashboard/dashboards/admin/software_management/panel.py b/cgcs_dashboard/dashboards/admin/software_management/panel.py new file mode 100755 index 00000000..59db8258 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/panel.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class SoftwareManagement(horizon.Panel): + name = _("Software Management") + slug = 'software_management' + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(SoftwareManagement, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + + +dashboard.Admin.register(SoftwareManagement) diff --git a/cgcs_dashboard/dashboards/admin/software_management/tables.py b/cgcs_dashboard/dashboards/admin/software_management/tables.py new file mode 100755 index 00000000..e6577543 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/tables.py @@ -0,0 +1,740 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import datetime +import logging + +from django.core.urlresolvers import reverse # noqa +from django.template.defaultfilters import title # noqa +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import messages +from horizon import tables +from openstack_dashboard import api + + +LOG = logging.getLogger(__name__) + + +class UploadPatch(tables.LinkAction): + name = "patchupload" + verbose_name = _("Upload Patches") + url = "horizon:admin:software_management:patchupload" + classes = ("ajax-modal", "btn-create") + icon = "plus" + + +class ApplyPatch(tables.BatchAction): + name = "apply" + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Apply Patch", + u"Apply Patches", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Applied Patch", + u"Applied Patches", + count + ) + + def allowed(self, request, patch=None): + if patch is None: + return True + return patch.repostate == "Available" + + def handle(self, table, request, obj_ids): + try: + result = api.patch.patch_apply_req(request, obj_ids) + messages.success(request, result) + except Exception as ex: + messages.error(request, ex.message) + + +class RemovePatch(tables.BatchAction): + name = "remove" + classes = () + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Remove Patch", + u"Remove Patchs", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Removed Patch", + u"Removed Patches", + count + ) + + def allowed(self, request, patch=None): + if patch is None: + return True + + if patch.unremovable == "Y": + if "disabled" not in self.classes: + self.classes = [c for c in self.classes] + ["disabled"] + else: + self.classes = [c for c in self.classes if c != "disabled"] + + return patch.repostate == "Applied" + + def handle(self, table, request, obj_ids): + try: + result = api.patch.patch_remove_req(request, obj_ids) + messages.success(request, result) + except Exception as ex: + messages.error(request, ex.message) + + +class DeletePatch(tables.BatchAction): + name = "delete" + icon = 'trash' + action_type = 'danger' + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Patch", + u"Delete Patches", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Patch", + u"Deleted Patches", + count + ) + + def allowed(self, request, patch=None): + if patch is None: + return True + return patch.repostate == "Available" + + def handle(self, table, request, obj_ids): + try: + result = api.patch.patch_delete_req(request, obj_ids) + messages.success(request, result) + except Exception as ex: + messages.error(request, ex.message) + + +class UpdatePatchRow(tables.Row): + ajax = True + + def get_data(self, request, patch_id): + patch = api.patch.get_patch(request, patch_id) + return patch + + +class PatchFilterAction(tables.FilterAction): + def filter(self, table, patches, filter_string): + """Naive case-insensitive search.""" + q = filter_string.lower() + + def comp(patch): + if q in patch.patch_id.lower(): + return True + return False + + return filter(comp, patches) + + +class PatchesTable(tables.DataTable): + PATCH_STATE_CHOICES = ( + (None, True), + ("", True), + ("none", True), + ("Available", True), + ("Partial-Apply", True), + ("Partial-Remove", True), + ("Applied", True), + ("Committed", True) + ) + + patch_id = tables.Column('patch_id', + link="horizon:admin:software_management:" + "patchdetail", + verbose_name=_('Patch ID')) + reboot_required = tables.Column('reboot_required', + verbose_name=_('RR')) + sw_version = tables.Column('sw_version', + verbose_name=_('Release')) + patchstate = tables.Column('patchstate', + verbose_name=_('Patch State'), + status=True, + status_choices=PATCH_STATE_CHOICES) + summary = tables.Column('summary', + verbose_name=_('Summary')) + + def get_object_id(self, obj): + return obj.patch_id + + def get_object_display(self, obj): + return obj.patch_id + + class Meta(object): + name = "patches" + multi_select = True + row_class = UpdatePatchRow + status_columns = ['patchstate'] + row_actions = (ApplyPatch, RemovePatch, DeletePatch) + table_actions = ( + PatchFilterAction, UploadPatch, ApplyPatch, RemovePatch, + DeletePatch) + verbose_name = _("Patches") + hidden_title = False + + +# Patch Orchestration +def get_cached_strategy(request, strategy_name, table): + if api.vim.STRATEGY_SW_PATCH == strategy_name: + if 'patchstrategy' not in table.kwargs: + table.kwargs['patchstrategy'] = api.vim.get_strategy( + request, strategy_name) + return table.kwargs['patchstrategy'] + elif api.vim.STRATEGY_SW_UPGRADE == strategy_name: + if 'upgradestrategy' not in table.kwargs: + table.kwargs['upgradestrategy'] = api.vim.get_strategy( + request, strategy_name) + return table.kwargs['upgradestrategy'] + + +class CreateStrategy(tables.LinkAction): + verbose_name = _("Create Strategy") + classes = ("ajax-modal", "btn-create") + icon = "plus" + + def allowed(self, request, datum): + try: + # Only a single strategy (patch or upgrade) can exist at a time. + strategy = get_cached_strategy(request, api.vim.STRATEGY_SW_PATCH, + self.table) + if not strategy: + strategy = get_cached_strategy(request, + api.vim.STRATEGY_SW_UPGRADE, + self.table) + + classes = [c for c in self.classes if c != "disabled"] + self.classes = classes + + if strategy: + if "disabled" not in self.classes: + self.classes = [c for c in self.classes] + ['disabled'] + except Exception as ex: + LOG.exception(ex) + return True + + +class CreatePatchStrategy(CreateStrategy): + name = "createpatchstrategy" + url = "horizon:admin:software_management:createpatchstrategy" + + +class CreateUpgradeStrategy(CreateStrategy): + name = "createupgradestrategy" + url = "horizon:admin:software_management:createupgradestrategy" + + +class DeleteStrategy(tables.Action): + force = False + disabled = False + requires_input = False + icon = 'trash' + action_type = 'danger' + verbose_name = _("Delete Strategy") + confirm_message = "You have selected Delete Strategy. " \ + "Please confirm your selection" + + def allowed(self, request, datum): + try: + strategy = get_cached_strategy(request, self.strategy_name, + self.table) + self.disabled = False + if not strategy or strategy.state in ['aborting', 'applying']: + self.disabled = True + except Exception as ex: + LOG.exception(ex) + return True + + def get_default_classes(self): + try: + if self.disabled: + return ['disabled'] + return super(DeleteStrategy, self).get_default_classes() + except Exception as ex: + LOG.exception(ex) + + def single(self, table, request, obj_id): + try: + result = api.vim.delete_strategy(request, self.strategy_name, + self.force) + if result: + messages.success(request, "Strategy Deleted") + else: + messages.error(request, "Strategy delete failed") + except Exception as ex: + LOG.exception(ex) + messages.error(request, ex.message) + + +class DeletePatchStrategy(DeleteStrategy): + name = "delete_patch_strategy" + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class DeleteUpgradeStrategy(DeleteStrategy): + name = "delete_upgrade_strategy" + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +class ApplyStrategy(tables.Action): + requires_input = False + disabled = False + verbose_name = _("Apply Strategy") + + def allowed(self, request, datum): + try: + strategy = get_cached_strategy(request, self.strategy_name, + self.table) + self.disabled = False + if not strategy or strategy.current_phase == 'abort' or \ + strategy.state in ['build-failed', 'applying', + 'applied', 'aborting', 'aborted']: + self.disabled = True + except Exception as ex: + LOG.exception(ex) + return True + + def get_default_classes(self): + try: + if self.disabled: + return ['disabled'] + return super(ApplyStrategy, self).get_default_classes() + except Exception as ex: + LOG.exception(ex) + + def single(self, table, request, obj_id): + try: + result = api.vim.apply_strategy(request, self.strategy_name) + if result: + messages.success(request, "Strategy apply in progress") + else: + messages.error(request, "Strategy apply failed") + except Exception as ex: + LOG.exception(ex) + messages.error(request, ex.message) + + +class ApplyPatchStrategy(ApplyStrategy): + name = "apply_patch_strategy" + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class ApplyUpgradeStrategy(ApplyStrategy): + name = "apply_upgrade_strategy" + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +class AbortStrategy(tables.Action): + requires_input = False + disabled = False + action_type = 'danger' + verbose_name = _("Abort Strategy") + confirm_message = "You have selected Abort Strategy. " \ + "Please confirm your selection" + + def allowed(self, request, datum): + try: + strategy = get_cached_strategy(request, self.strategy_name, + self.table) + self.disabled = False + if not strategy or strategy.state != 'applying': + self.disabled = True + except Exception as ex: + LOG.exception(ex) + return True + + def get_default_classes(self): + try: + if self.disabled: + return ['disabled'] + return super(AbortStrategy, self).get_default_classes() + except Exception as ex: + LOG.exception(ex) + + def single(self, table, request, obj_id): + try: + result = api.vim.abort_strategy(request, self.strategy_name) + if result: + messages.success(request, "Strategy abort in progress") + else: + messages.error(request, "Strategy abort failed") + except Exception as ex: + LOG.exception(ex) + messages.error(request, ex.message) + + +class AbortPatchStrategy(AbortStrategy): + name = "abort_patch_strategy" + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class AbortUpgradeStrategy(AbortStrategy): + name = "abort_upgrade_strategy" + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +class ApplyStage(tables.BatchAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Apply Stage", + u"Apply Stages", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Applied Stage", + u"Applied Stages", + count + ) + + def allowed(self, request, stage=None): + try: + strategy = get_cached_strategy(request, self.strategy_name, + self.table) + if stage.result != "initial" or stage.inprogress \ + or not strategy or strategy.current_phase == 'abort' \ + or strategy.state in ['aborted', 'aborting']: + return False + # Loop through the stages and ensure this is the first in 'line' + stages = api.vim.get_stages(request, self.strategy_name) + for s in stages: + if s.phase.phase_name == stage.phase.phase_name and \ + s.stage_id == stage.stage_id and \ + stage.result == 'initial': + return True + if s.result == 'initial' or s.inprogress: + return False + except Exception as ex: + LOG.exception(ex) + return False + + def handle(self, table, request, obj_ids): + for obj_id in obj_ids: + try: + stage_id = obj_id.split('-', 1)[1] + result = api.vim.apply_strategy(request, self.strategy_name, + stage_id) + if result is None: + messages.error(request, "Strategy stage %s apply failed" % + stage_id) + else: + messages.success(request, + "Strategy stage %s apply in progress" % + stage_id) + except Exception as ex: + LOG.exception(ex) + messages.error(request, ex.message) + + +class ApplyPatchStage(ApplyStage): + name = "apply_patch_stage" + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class ApplyUpgradeStage(ApplyStage): + name = "apply_upgrade_stage" + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +class AbortStage(tables.BatchAction): + name = "abort_stage" + action_type = 'danger' + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Abort Stage", + u"Abort Stages", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Aborted Stage", + u"Aborted Stages", + count + ) + + def allowed(self, request, stage=None): + if not stage: + return True + try: + strategy = get_cached_strategy(request, self.strategy_name, + self.table) + if not strategy or strategy.current_phase == 'abort' \ + or strategy.state in ['aborted', 'aborting']: + return False + except Exception as ex: + LOG.exception(ex) + return stage.inprogress and stage.phase.phase_name == 'apply' + + def handle(self, table, request, obj_ids): + for obj_id in obj_ids: + try: + stage_id = obj_id.split('-', 1)[1] + result = api.vim.abort_strategy(request, self.strategy_name, + stage_id) + if result is None: + messages.error(request, + "Strategy stage %s abort in progress" % + stage_id) + else: + messages.success(request, "Strategy stage %s aborted" % + stage_id) + except Exception as ex: + LOG.exception(ex) + messages.error(request, ex.message) + + +class AbortPatchStage(AbortStage): + name = "abort_patch_stage" + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class AbortUpgradeStage(AbortStage): + name = "abort_upgrade_stage" + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +def get_current_step_time(stage): + if stage.current_step >= len(stage.steps): + return "N/A" + return get_time(stage.steps[stage.current_step]) + + +def get_time(entity): + try: + return datetime.timedelta(seconds=entity.timeout) + except Exception: + return "N/A" + + +def get_current_step_name(stage): + if stage.current_step >= len(stage.steps): + return "N/A" + return stage.steps[stage.current_step].step_name + + +def get_entities(stage): + for step in stage.steps: + if step.step_name == 'sw-patch-hosts': + return ", ".join(step.entity_names) + elif step.step_name == 'upgrade-hosts': + return ", ".join(step.entity_names) + return "N/A" + + +FAILURE_STATUSES = ('failed', 'aborted') +STAGE_STATE_CHOICES = ( + (None, True), + ("", True), + ("none", True), + ("success", True), + ("initial", True), + ("failed", False), + ("timed-out", False), + ("aborted", False), +) + + +def get_result(stage): + if stage.phase.phase_name == 'build' and stage.phase.result == 'failed': + # Special case for build failures + return stage.phase.result + if stage.inprogress: + return "In Progress (Step " + str(stage.current_step + 1) + "/" + \ + str(stage.total_steps) + ")" + return stage.result + + +def get_reason(stage): + if stage.phase.phase_name == 'build' and stage.phase.reason: + # Special case for build failures + return stage.phase.reason + if stage.reason or \ + stage.current_step >= len(stage.steps): + return stage.reason + return stage.steps[stage.current_step].reason + + +def get_phase_name(stage): + return title(stage.phase.phase_name) + + +class UpdateStageRow(tables.Row): + ajax = True + + def get_data(self, request, row_id): + phase = row_id.split('-', 1)[0] + stage_id = row_id.split('-', 1)[1] + stage = api.vim.get_stage(request, self.strategy_name, phase, stage_id) + return stage + + +class UpdatePatchStageRow(UpdateStageRow): + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class UpdateUpgradeStageRow(UpdateStageRow): + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +class StagesTable(tables.DataTable): + + phase = tables.Column(get_phase_name, verbose_name=_('Phase')) + stage_name = tables.Column('stage_name') + entities = tables.Column(get_entities, verbose_name=_('Hosts')) + current_step = tables.Column(get_current_step_name, + verbose_name=_('Current Step Name')) + step_timeout = tables.Column(get_current_step_time, + verbose_name=_('Current Step Timeout'),) + status = tables.Column(get_result, + status=True, + status_choices=STAGE_STATE_CHOICES, + verbose_name=_('Status')) + reason = tables.Column(get_reason, + verbose_name=_('Reason')) + + def get_object_id(self, obj): + return "%s-%s" % (obj.phase.phase_name, obj.stage_id) + + def get_object_display(self, obj): + return "Stage %s of %s Phase" % (obj.stage_id, obj.phase.phase_name) + + +def get_patchstage_link_url(stage): + return reverse("horizon:admin:software_management:patchstagedetail", + args=(stage.stage_id, stage.phase.phase_name)) + + +class PatchStagesTable(StagesTable): + stage_name = tables.Column('stage_name', + link=get_patchstage_link_url, + verbose_name=_('Stage Name')) + + class Meta(object): + name = "patchstages" + multi_select = False + status_columns = ['status', ] + row_class = UpdatePatchStageRow + row_actions = (ApplyPatchStage, AbortPatchStage) + table_actions = (CreatePatchStrategy, ApplyPatchStrategy, + AbortPatchStrategy, DeletePatchStrategy) + verbose_name = _("Stages") + hidden_title = False + + +def get_upgradestage_link_url(stage): + return reverse("horizon:admin:software_management:upgradestagedetail", + args=(stage.stage_id, stage.phase.phase_name)) + + +class UpgradeStagesTable(StagesTable): + stage_name = tables.Column('stage_name', + link=get_upgradestage_link_url, + verbose_name=_('Stage Name')) + + class Meta(object): + name = "upgradestages" + multi_select = False + status_columns = ['status', ] + row_class = UpdateUpgradeStageRow + row_actions = (ApplyUpgradeStage, AbortUpgradeStage) + table_actions = (CreateUpgradeStrategy, ApplyUpgradeStrategy, + AbortUpgradeStrategy, DeleteUpgradeStrategy) + verbose_name = _("Stages") + hidden_title = False + + +def display_entities(step): + return ", ".join(step.entity_names) + + +class UpdateStepRow(tables.Row): + ajax = True + + def get_data(self, request, row_id): + phase_name = row_id.split('-', 2)[0] + stage_id = row_id.split('-', 2)[1] + step_id = row_id.split('-', 2)[2] + step = api.vim.get_step( + request, self.strategy_name, phase_name, stage_id, step_id) + step.phase_name = phase_name + step.stage_id = stage_id + return step + + +class UpdatePatchStepRow(UpdateStepRow): + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class UpdateUpgradeStepRow(UpdateStepRow): + strategy_name = api.vim.STRATEGY_SW_UPGRADE + + +class StepsTable(tables.DataTable): + step_id = tables.Column('step_id', verbose_name=_('Step ID')) + step_name = tables.Column('step_name', verbose_name=_('Step Name')) + entities = tables.Column(display_entities, verbose_name=_('Entities')) + start = tables.Column('start_date_time', verbose_name=_('Start Time'),) + end = tables.Column('end_date_time', verbose_name=_('End Time'),) + timeout = tables.Column(get_time, verbose_name=_('Timeout'),) + result = tables.Column('result', + status=True, + status_choices=STAGE_STATE_CHOICES, + verbose_name=_('Status')) + reason = tables.Column('reason', verbose_name=_('Reason')) + + def get_object_id(self, obj): + return "%s-%s-%s" % (obj.phase_name, obj.stage_id, obj.step_id) + + +class PatchStepsTable(StepsTable): + class Meta(object): + name = "steps" + status_columns = ['result', ] + row_class = UpdatePatchStepRow + verbose_name = _("Steps") + hidden_title = False + + +class UpgradeStepsTable(StepsTable): + class Meta(object): + name = "steps" + status_columns = ['result', ] + row_class = UpdateUpgradeStepRow + verbose_name = _("Steps") + hidden_title = False diff --git a/cgcs_dashboard/dashboards/admin/software_management/tabs.py b/cgcs_dashboard/dashboards/admin/software_management/tabs.py new file mode 100755 index 00000000..5fbb7b1e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/tabs.py @@ -0,0 +1,134 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from cgtsclient.common import constants +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.software_management import \ + tables as toplevel_tables + +LOG = logging.getLogger(__name__) + + +class PatchesTab(tabs.TableTab): + table_classes = (toplevel_tables.PatchesTable,) + name = _("Patches") + slug = "patches" + template_name = ("admin/software_management/_patches.html") + + def get_context_data(self, request): + context = super(PatchesTab, self).get_context_data(request) + + phosts = [] + try: + phosts = api.patch.get_hosts(request) + except Exception: + exceptions.handle(request, + _('Unable to retrieve host list.')) + + context['patch_current'] = all( + phost.patch_current is True for phost in phosts) + + return context + + def get_patches_data(self): + request = self.request + patches = [] + try: + patches = api.patch.get_patches(request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve patch list.')) + + return patches + + +class PatchOrchestrationTab(tabs.TableTab): + table_classes = (toplevel_tables.PatchStagesTable,) + name = _("Patch Orchestration") + slug = "patch_orchestration" + template_name = ("admin/software_management/_patch_orchestration.html") + + def get_context_data(self, request): + context = super(PatchOrchestrationTab, self).get_context_data(request) + + strategy = None + try: + strategy = api.vim.get_strategy(request, api.vim.STRATEGY_SW_PATCH) + except Exception as ex: + LOG.exception(ex) + exceptions.handle(request, + _('Unable to retrieve current strategy.')) + + context['strategy'] = strategy + return context + + def get_patchstages_data(self): + request = self.request + stages = [] + try: + stages = api.vim.get_stages(request, api.vim.STRATEGY_SW_PATCH) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve stages list.')) + + return stages + + +class UpgradeOrchestrationTab(tabs.TableTab): + table_classes = (toplevel_tables.UpgradeStagesTable,) + name = _("Upgrade Orchestration") + slug = "upgrade_orchestration" + template_name = ("admin/software_management/_upgrade_orchestration.html") + + def get_context_data(self, request): + context = super(UpgradeOrchestrationTab, self).get_context_data( + request) + + strategy = None + try: + strategy = api.vim.get_strategy(request, + api.vim.STRATEGY_SW_UPGRADE) + except Exception as ex: + LOG.exception(ex) + exceptions.handle(request, + _('Unable to retrieve current strategy.')) + + context['strategy'] = strategy + return context + + def get_upgradestages_data(self): + request = self.request + stages = [] + try: + stages = api.vim.get_stages(request, api.vim.STRATEGY_SW_UPGRADE) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve stages list.')) + + return stages + + def allowed(self, request): + # Upgrade orchestration not available on CPE deployments + systems = api.sysinv.system_list(request) + system_type = systems[0].to_dict().get('system_type') + if system_type == constants.TS_AIO: + return False + return True + + +class SoftwareManagementTabs(tabs.TabGroup): + slug = "software_management_tabs" + tabs = (PatchesTab, PatchOrchestrationTab, UpgradeOrchestrationTab) + sticky = True diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_patch_strategy.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_patch_strategy.html new file mode 100755 index 00000000..c9ee298e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_patch_strategy.html @@ -0,0 +1,35 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_action %}{% url 'horizon:admin:software_management:createpatchstrategy' %}{% endblock %} + +{% block modal-header %}{% trans "Create Patch Strategy" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    + {% trans "Create a strategy to specify how the system should be patched." %} +

    +

    + {% trans "There are " %}{{ alarms }}{% trans " active alarms on the system, " %}{{ affecting }}{% trans " of which are management affecting." %} +
    + {% if affecting > 0 %} + {% trans "These management affecting alarms must be addressed before orchestration will be successful" %} + {% else %} + {% trans "These alarms can be ignored by choosing relaxed alarm restrictions." %} + {% endif %} +

    + +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_upgrade_strategy.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_upgrade_strategy.html new file mode 100755 index 00000000..90d6fa4e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_create_upgrade_strategy.html @@ -0,0 +1,35 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_action %}{% url 'horizon:admin:software_management:createupgradestrategy' %}{% endblock %} + +{% block modal-header %}{% trans "Create Upgrade Strategy" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    + {% trans "Create a strategy to specify how the system should be upgraded." %} +

    +

    + {% trans "There are " %}{{ alarms }}{% trans " active alarms on the system, " %}{{ affecting }}{% trans " of which are management affecting." %} +
    + {% if affecting > 0 %} + {% trans "These management affecting alarms must be addressed before orchestration will be successful" %} + {% else %} + {% trans "These alarms can be ignored by choosing relaxed alarm restrictions." %} + {% endif %} +

    + +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_patches.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_patches.html new file mode 100755 index 00000000..c3797ae7 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_patches.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Patch Details" %}{% endblock %} + +{% block main %} +

    {{patch.patch_id }}

    + +
    +
    +
    +

    {% trans "Info" %}

    +
    +
    +
    {% trans "Patch State" %}
    +
    {{ patch.patchstate }}
    +
    {% trans "Platform Release Version" %}
    +
    {{ patch.sw_version }}
    +
    {% trans "Reboot-Required" %}
    +
    {{ patch.reboot_required }}
    +
    {% trans "Patch Status Code" %}
    +
    {{ patch.status }}
    + {% if patch.unremovable == "Y" %} +
    {% trans "Unremovable" %}
    +
    {% trans "This patch cannot be removed" %}
    + {% endif %} +
    {% trans "Required Patches" %}
    +
    {{ patch.requires_display|linebreaks }}
    +
    +
    +
    {% trans "Summary" %}
    +
    {{ patch.summary|linebreaks }}
    +
    +
    +
    {% trans "Description" %}
    +
    {{ patch.description|linebreaks }}
    +
    +
    +
    {% trans "Warnings" %}
    +
    {{ patch.warnings|linebreaks }}
    +
    +
    +
    {% trans "Installation Instructions" %}
    +
    {{ patch.install_instructions|linebreaks }}
    +
    +
    +
    {% trans "Contents" %}
    +
    {{ patch.contents_display|linebreaks }}
    +
    +
    +
    +
    +{% endblock %} + + diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_stage.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_stage.html new file mode 100755 index 00000000..7ad5dac4 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_detail_stage.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Stage Details" %}{% endblock %} + +{% block main %} +

    Stage {{ stage.stage_id }} of {{ stage.phase.phase_name|title }} Phase

    + +
    +
    +
    +

    {% trans "Info" %}

    +
    +
    +
    +
    Stage ID
    +
    {{ stage.stage_id }}
    +
    Stage Name
    +
    {{ stage.stage_name }}
    +
    Total Steps
    +
    {{ stage.total_steps }}
    +
    Current Step
    +
    {{ stage.current_step }}
    +
    Timeout
    +
    {{ stage.timeout_display }}
    +
    Start Date/Time
    +
    {{ stage.start_date_time }}
    +
    End Date/Time
    +
    {{ stage.end_date_time }}
    +
    Status
    + {% if stage.inprogress %} +
    {% trans "In Progress" %}
    + {% else %} +
    {{ stage.result }}
    + {% endif %} +
    Reason
    +
    {{ stage.reason }}
    +
    +
    + + {{ steps_table.render }} +
    +
    +
    +{% endblock %} + +{% block js %} + {{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patch_orchestration.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patch_orchestration.html new file mode 100755 index 00000000..036897a1 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patch_orchestration.html @@ -0,0 +1,43 @@ +{% load i18n sizeformat %} + +{% block main %} + +
    +

    {% trans "Patch Strategy" %}

    +
    +
    + + {% if strategy %} +
    +
    {% trans "Controller Apply Type" %}
    +
    {{ strategy.controller_apply_type }}
    +
    {% trans "Storage Apply Type" %}
    +
    {{ strategy.storage_apply_type }}
    +
    {% trans "Compute Apply Type" %}
    +
    {{ strategy.compute_apply_type }}
    + {% if strategy.compute_apply_type == "parallel" %} +
    {% trans "Maximum Parallel Compute Hosts" %}
    +
    {{ strategy.max_parallel_compute_hosts }}
    + {% endif %} +
    {% trans "Default Instance Action" %}
    +
    {{ strategy.default_instance_action }}
    +
    {% trans "Alarm Restrictions" %}
    +
    {{ strategy.alarm_restrictions }}
    +
    +
    +
    {% trans "Current Phase" %}
    +
    {{ strategy.current_phase }}
    +
    {% trans "Current Phase Completion" %}
    +
    {{ strategy.current_phase_completion_percentage }}%
    +
    {% trans "State" %}
    +
    {{ strategy.state }}
    +
    + {% else %} + {% trans "No Strategy has been created" %} + {% endif %} +
    +
    +
    + {{ patchstages_table.render }} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patches.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patches.html new file mode 100755 index 00000000..a76c23db --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_patches.html @@ -0,0 +1,20 @@ +{% load i18n sizeformat %} + +{% block main %} +
    +
    + + {% if patch_current %} + {% trans "System is Patch Current" %} + {% else %} + {% trans "System is Not Patch Current" %} + {% endif %} + +
    + + {{ patches_table.render }} +
    +{% endblock %} + + + diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upgrade_orchestration.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upgrade_orchestration.html new file mode 100755 index 00000000..8dc02343 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upgrade_orchestration.html @@ -0,0 +1,39 @@ +{% load i18n sizeformat %} + +{% block main %} + +
    +

    {% trans "Upgrade Strategy" %}

    +
    +
    + + {% if strategy %} +
    +
    {% trans "Storage Apply Type" %}
    +
    {{ strategy.storage_apply_type }}
    +
    {% trans "Compute Apply Type" %}
    +
    {{ strategy.compute_apply_type }}
    + {% if strategy.compute_apply_type == "parallel" %} +
    {% trans "Maximum Parallel Compute Hosts" %}
    +
    {{ strategy.max_parallel_compute_hosts }}
    + {% endif %} +
    {% trans "Alarm Restrictions" %}
    +
    {{ strategy.alarm_restrictions }}
    +
    +
    +
    {% trans "Current Phase" %}
    +
    {{ strategy.current_phase }}
    +
    {% trans "Current Phase Completion" %}
    +
    {{ strategy.current_phase_completion_percentage }}%
    +
    {% trans "State" %}
    +
    {{ strategy.state }}
    +
    + {% else %} + {% trans "No Strategy has been created" %} + {% endif %} +
    +
    +
    + {{ upgradestages_table.render }} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upload_patch.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upload_patch.html new file mode 100755 index 00000000..c95491ee --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/_upload_patch.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_action %}{% url 'horizon:admin:software_management:patchupload' %}{% endblock %} +{% block form_attrs %}enctype="multipart/form-data"{% endblock %} + +{% block modal-header %}{% trans "Upload Patches" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    + {% trans "Specify one or more patch files to upload to the Patching Service." %} +

    + +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_patch_strategy.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_patch_strategy.html new file mode 100755 index 00000000..819619c0 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_patch_strategy.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Patch Strategy" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Patch Strategy") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/software_management/_create_patch_strategy.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_upgrade_strategy.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_upgrade_strategy.html new file mode 100755 index 00000000..166c3433 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/create_upgrade_strategy.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Upgrade Strategy" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Upgrade Strategy") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/software_management/_create_upgrade_strategy.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/index.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/index.html new file mode 100755 index 00000000..d14de189 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/index.html @@ -0,0 +1,45 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Software Management" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Software Management") %} +{% endblock page_header %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} + +{% block js %} + {{ block.super }} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/upload_patch.html b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/upload_patch.html new file mode 100755 index 00000000..95fd8ca5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/templates/software_management/upload_patch.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Upload Patches" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Upload Patches") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/inventory/_upload_patch.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/software_management/urls.py b/cgcs_dashboard/dashboards/admin/software_management/urls.py new file mode 100755 index 00000000..3b551758 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/urls.py @@ -0,0 +1,41 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.conf.urls import url + +from openstack_dashboard.dashboards.admin.software_management.views import \ + CreatePatchStrategyView +from openstack_dashboard.dashboards.admin.software_management.views import \ + CreateUpgradeStrategyView +from openstack_dashboard.dashboards.admin.software_management.views import \ + DetailPatchStageView +from openstack_dashboard.dashboards.admin.software_management.views import \ + DetailPatchView +from openstack_dashboard.dashboards.admin.software_management.views import \ + DetailUpgradeStageView +from openstack_dashboard.dashboards.admin.software_management.views import \ + IndexView +from openstack_dashboard.dashboards.admin.software_management.views import \ + UploadPatchView + + +urlpatterns = [ + url(r'^$', IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/patchdetail/$', + DetailPatchView.as_view(), name='patchdetail'), + url(r'^patchupload/$', UploadPatchView.as_view(), + name='patchupload'), + url(r'^(?P[^/]+)/phase/(?P[^/]+)/patchstagedetail/$', + DetailPatchStageView.as_view(), name='patchstagedetail'), + url(r'^(?P[^/]+)/phase/(?P[^/]+)/upgradestagedetail/$', + DetailUpgradeStageView.as_view(), name='upgradestagedetail'), + url(r'^createpatchstrategy/$', CreatePatchStrategyView.as_view(), + name='createpatchstrategy'), + url(r'^createupgradestrategy/$', CreateUpgradeStrategyView.as_view(), + name='createupgradestrategy') +] diff --git a/cgcs_dashboard/dashboards/admin/software_management/views.py b/cgcs_dashboard/dashboards/admin/software_management/views.py new file mode 100755 index 00000000..1c3aed9d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/software_management/views.py @@ -0,0 +1,166 @@ +# +# Copyright (c) 2013-2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import datetime +import logging + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon import tabs +from horizon import views +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.software_management.forms import \ + CreatePatchStrategyForm +from openstack_dashboard.dashboards.admin.software_management.forms import \ + CreateUpgradeStrategyForm +from openstack_dashboard.dashboards.admin.software_management.forms import \ + UploadPatchForm +from openstack_dashboard.dashboards.admin.software_management import \ + tables as toplevel_tables +from openstack_dashboard.dashboards.admin.software_management.tabs \ + import SoftwareManagementTabs + +LOG = logging.getLogger(__name__) + + +class IndexView(tabs.TabbedTableView): + tab_group_class = SoftwareManagementTabs + template_name = 'admin/software_management/index.html' + page_title = _("Software Management") + + def get_tabs(self, request, *args, **kwargs): + return self.tab_group_class(request, **kwargs) + + +class DetailPatchView(views.HorizonTemplateView): + template_name = 'admin/software_management/_detail_patches.html' + page_title = 'Patch Detail' + + def get_context_data(self, **kwargs): + context = super(DetailPatchView, self).get_context_data(**kwargs) + context["patch"] = self.get_data() + return context + + def get_data(self): + if not hasattr(self, "_patch"): + patch_id = self.kwargs['patch_id'] + try: + patch = api.patch.get_patch(self.request, patch_id) + patch.contents_display = "%s" % "\n".join( + filter(None, patch.contents)) + patch.requires_display = "%s" % "\n".join( + filter(None, patch.requires)) + except Exception: + redirect = reverse('horizon:admin:software_management:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'patch "%s".') % patch_id, + redirect=redirect) + + self._patch = patch + return self._patch + + +class UploadPatchView(forms.ModalFormView): + form_class = UploadPatchForm + template_name = 'admin/software_management/upload_patch.html' + context_object_name = 'patch' + success_url = reverse_lazy("horizon:admin:software_management:index") + + +class CreatePatchStrategyView(forms.ModalFormView): + form_class = CreatePatchStrategyForm + template_name = 'admin/software_management/create_patch_strategy.html' + context_object_name = 'strategy' + success_url = reverse_lazy("horizon:admin:software_management:index") + + def get_context_data(self, **kwargs): + context = super(CreatePatchStrategyView, self).get_context_data( + **kwargs) + alarms = api.sysinv.alarm_list(self.request) + affecting = \ + len([alarm for alarm in alarms if alarm.mgmt_affecting == 'True']) + + context['alarms'] = len(alarms) + context['affecting'] = affecting + return context + + +class CreateUpgradeStrategyView(forms.ModalFormView): + form_class = CreateUpgradeStrategyForm + template_name = 'admin/software_management/create_upgrade_strategy.html' + context_object_name = 'strategy' + success_url = reverse_lazy("horizon:admin:software_management:index") + + def get_context_data(self, **kwargs): + context = super(CreateUpgradeStrategyView, self).get_context_data( + **kwargs) + alarms = api.sysinv.alarm_list(self.request) + affecting = \ + len([alarm for alarm in alarms if alarm.mgmt_affecting == 'True']) + + context['alarms'] = len(alarms) + context['affecting'] = affecting + return context + + +class DetailStageView(tables.DataTableView): + template_name = 'admin/software_management/_detail_stage.html' + page_title = 'Stage Detail' + + def get_context_data(self, **kwargs): + context = super(DetailStageView, self).get_context_data(**kwargs) + context["stage"] = self.get_stage() + return context + + def get_stage(self): + if not hasattr(self, "_stage"): + phase = self.kwargs['phase'] + stage_id = self.kwargs['stage_id'] + try: + stage = api.vim.get_stage(self.request, self.strategy_name, + phase, stage_id) + stage.timeout_display = \ + datetime.timedelta(seconds=stage.timeout) + except Exception: + redirect = reverse('horizon:admin:software_management:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'stage "%s".') % stage_id, + redirect=redirect) + + self._stage = stage + return self._stage + + def get_data(self): + try: + stage = self.get_stage() + steps = stage.steps + for step in steps: + step.phase_name = stage.phase.phase_name + step.stage_id = stage.stage_id + except Exception: + steps = [] + msg = _('Steps list can not be retrieved.') + exceptions.handle(self.request, msg) + return steps + + +class DetailPatchStageView(DetailStageView): + table_class = toplevel_tables.PatchStepsTable + strategy_name = api.vim.STRATEGY_SW_PATCH + + +class DetailUpgradeStageView(DetailStageView): + table_class = toplevel_tables.UpgradeStepsTable + strategy_name = api.vim.STRATEGY_SW_UPGRADE diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/__init__.py b/cgcs_dashboard/dashboards/admin/storage_overview/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/constants.py b/cgcs_dashboard/dashboards/admin/storage_overview/constants.py new file mode 100644 index 00000000..c7795979 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/constants.py @@ -0,0 +1,12 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +PREFIX = 'admin/storage_overview/' +STORAGE_OVERVIEW_TEMPLATE_NAME = PREFIX + 'storage_overview.html' +STORAGE_SERVICE_DETAIL_TEMPLATE_NAME = PREFIX + '_detail_storageservices.html' +STORAGE_USAGE_TEMPLATE_NAME = PREFIX + '_usage.html' diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/panel.py b/cgcs_dashboard/dashboards/admin/storage_overview/panel.py new file mode 100755 index 00000000..f18512f8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/panel.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class StorageOverview(horizon.Panel): + name = _("Storage Overview") + slug = 'storage_overview' + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(StorageOverview, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + + +dashboard.Admin.register(StorageOverview) diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/tables.py b/cgcs_dashboard/dashboards/admin/storage_overview/tables.py new file mode 100644 index 00000000..1555670e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/tables.py @@ -0,0 +1,58 @@ +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# +from django.utils.translation import ugettext_lazy as _ +from horizon import tables + + +class UsageTable(tables.DataTable): + service = tables.Column('service_name', verbose_name=_('Service name')) + backend = tables.Column('backend_name', verbose_name=_('Backend name')) + capacity = \ + tables.Column('total_capacity', verbose_name=_('Total Capacity (GiB)')) + free = \ + tables.Column('free_capacity', verbose_name=_('Free Capacity (GiB)')) + + class Meta(object): + name = "usage" + hidden_title = False + verbose_name = _("Storage Services and Backends Usage") + multi_select = False + + def get_object_id(self, datum): + return datum + + +class MonitorsTable(tables.DataTable): + host = tables.Column('host', + verbose_name=_('Host')) + rank = tables.Column('rank', + verbose_name=_('Rank')) + + def get_object_id(self, obj): + return obj.host # hostname is always unique + + class Meta(object): + name = "monitors" + verbose_name = _("Monitors") + multi_select = False + + +class OSDsTable(tables.DataTable): + host = tables.Column('host', + verbose_name=_('Host')) + name = tables.Column('name', + verbose_name=_('Name')) + status = tables.Column('status', + verbose_name=_('Status')) + + def get_object_id(self, obj): + return obj.id + + class Meta(object): + name = "osds" + verbose_name = _("OSDs") + multi_select = False diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/tabs.py b/cgcs_dashboard/dashboards/admin/storage_overview/tabs.py new file mode 100644 index 00000000..8707a22e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/tabs.py @@ -0,0 +1,94 @@ +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs +from openstack_dashboard.api import base +from openstack_dashboard.api import ceph +from openstack_dashboard.api import sysinv +from openstack_dashboard.dashboards.admin.storage_overview import constants +from openstack_dashboard.dashboards.admin.storage_overview import tables + +LOG = logging.getLogger(__name__) + + +class StorageServicesTab(tabs.TableTab): + table_classes = (tables.MonitorsTable, tables.OSDsTable,) + name = _("Services") + slug = "storage_services" + template_name = constants.STORAGE_SERVICE_DETAIL_TEMPLATE_NAME + + def get_monitors_data(self): + try: + return ceph.monitor_list() + except Exception as e: + LOG.error(e) + return + + def get_osds_data(self): + try: + return ceph.osd_list() + except Exception as e: + LOG.error(e) + return + + def get_cluster_data(self): + try: + return ceph.cluster_get() + except Exception as e: + LOG.error(e) + return + + def get_storage_data(self): + try: + return ceph.storage_get() + except Exception as e: + LOG.error(e) + return + + def get_context_data(self, request): + try: + context = super(StorageServicesTab, self).get_context_data(request) + context['monitors'] = self.get_monitors_data() + context['osds'] = self.get_osds_data() + context['cluster'] = self.get_cluster_data() + context['storage'] = self.get_storage_data() + return context + except Exception as e: + LOG.error(e) + msg = _('Unable to get storage services list.') + exceptions.check_message(["Connection", "refused"], msg) + return + + def allowed(self, request): + return base.is_TiS_region(request) + + +class StorageUsageTab(tabs.TableTab): + table_classes = (tables.UsageTable,) + name = _("Usage") + slug = "usage" + template_name = constants.STORAGE_USAGE_TEMPLATE_NAME + + def get_usage_data(self): + try: + return sysinv.storage_usage_list(self.request) + except Exception as e: + LOG.error(e) + + return [] + + +class StorageOverviewTabs(tabs.TabGroup): + slug = "storage_overview" + tabs = (StorageServicesTab, StorageUsageTab) + sticky = True diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_detail_storageservices.html b/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_detail_storageservices.html new file mode 100644 index 00000000..7aee2543 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_detail_storageservices.html @@ -0,0 +1,55 @@ +{% load i18n sizeformat %} + +{% block main %} + {% autoescape off %} +
    + {% if cluster %} +
    +
    +
    {% trans "Storage Cluster UUID " %}
    +
    {{ cluster.fsid }}
    +
    {% trans "Storage Cluster Health Status" %}
    +
    {{ cluster.health }}
    +
    {% trans "Storage Cluster Health Details" %}
    +
    {{ cluster.detail }}
    +
    +
    + + {% if storage %} +
    +
    +
    {% trans "Storage Usage" %}
    +
    {{ storage.used }} {{" MiB used, "}} + {{ storage.available }} {{" GiB available, "}} + {{ storage.total }} {{" GiB total"}}
    +
    {% trans "Storage I/O" %}
    +
    {{ storage.writes_per_sec }} {{" kiB/s write, "}} + {{ storage.operations_per_sec }} {{" operations/second"}}
    +
    +
    + {% else %} +
    {% trans "No storage information available" %}
    + {% endif %} + + {% if monitors %} +
    + {{ monitors_table.render }} +
    + {% else %} +
    {% trans "No monitor information available" %}
    + {% endif %} + + {% if osds %} +
    + {{ osds_table.render }} +
    + {% else %} +
    {% trans "No OSD information available" %}
    + {% endif %} + {% else %} +
    {% trans "No cluster information available" %}
    + {% endif %} + +
    + {% endautoescape %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_usage.html b/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_usage.html new file mode 100644 index 00000000..150b9db3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/_usage.html @@ -0,0 +1,5 @@ +{% load i18n sizeformat %} + +{% block main %} + {{ usage_table.render }} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/storage_overview.html b/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/storage_overview.html new file mode 100644 index 00000000..4180cee5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/templates/storage_overview/storage_overview.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Storage Overview" %}{% endblock %} +{% block main %} +
    + {{ tab_group.render }} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/urls.py b/cgcs_dashboard/dashboards/admin/storage_overview/urls.py new file mode 100644 index 00000000..6fb7dacd --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/urls.py @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import url +from openstack_dashboard.dashboards.admin.storage_overview import views + +urlpatterns = [ + url(r'^$', views.StorageOverview.as_view(), name='index') +] diff --git a/cgcs_dashboard/dashboards/admin/storage_overview/views.py b/cgcs_dashboard/dashboards/admin/storage_overview/views.py new file mode 100644 index 00000000..b4d0953c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/storage_overview/views.py @@ -0,0 +1,22 @@ +# +# Copyright (c) 2016 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ +from horizon import tabs + +from openstack_dashboard.dashboards.admin.storage_overview import ( + tabs as project_tabs +) +from openstack_dashboard.dashboards.admin.storage_overview import constants + + +class StorageOverview(tabs.TabbedTableView): + tab_group_class = project_tabs.StorageOverviewTabs + template_name = constants.STORAGE_OVERVIEW_TEMPLATE_NAME + page_title = _("Storage Overview") diff --git a/cgcs_dashboard/dashboards/admin/system_config/__init__.py b/cgcs_dashboard/dashboards/admin/system_config/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/system_config/address_pools/__init__.py b/cgcs_dashboard/dashboards/admin/system_config/address_pools/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/admin/system_config/address_pools/forms.py b/cgcs_dashboard/dashboards/admin/system_config/address_pools/forms.py new file mode 100755 index 00000000..b3c3d1e7 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/address_pools/forms.py @@ -0,0 +1,141 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2015 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +import logging + +from django.core.urlresolvers import reverse +from django import shortcuts +from django.utils.translation import ugettext_lazy as _ +import netaddr + +from horizon import forms +from horizon import messages +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + +ALLOCATION_ORDER_CHOICES = ( + ('sequential', _("Sequential")), + ('random', _("Random")), +) + + +class CreateAddressPool(forms.SelfHandlingForm): + success_url = 'horizon:admin:system_config:index' + failure_url = 'horizon:admin:system_config:index' + + name = forms.CharField(max_length=255, + label=_("Name"), + required=True, + min_length=1) + + network = forms.IPField( + label=_("Network Address"), + required=True, + initial="", + help_text=_("Network IP interface address in CIDR format " + "(e.g. 192.168.0.0/24, 2001:DB8::/48"), + version=forms.IPv4 | forms.IPv6, + mask=True) + + order = forms.ChoiceField( + label=_("Allocation Order"), + required=True, + initial='random', + choices=ALLOCATION_ORDER_CHOICES) + + ranges = forms.CharField( + label=_("Address Ranges"), + required=False, + help_text=(_("Enter a comma separated list of - " + "IP address ranges."))) + + def _parse_ranges(self, range_str): + result = [] + for r in range_str.replace(' ', '').split(','): + start, end = r.split('-') + result.append([start, end]) + return result + + def clean_ranges(self): + try: + ranges = self.cleaned_data.get('ranges') + return self._parse_ranges(ranges) if ranges else None + except Exception: + msg = (_("Expecting a comma separated list " + "of - IP address ranges")) + raise forms.ValidationError(msg) + + def handle(self, request, data): + try: + ip_address = netaddr.IPNetwork(data['network']) + body = {'name': data['name'], + 'network': str(ip_address.ip), + 'prefix': ip_address.prefixlen, + 'order': data['order']} + if data.get('ranges'): + body['ranges'] = data.get('ranges') + pool = api.sysinv.address_pool_create(request, **body) + msg = (_('Address pool was successfully created')) + messages.success(request, msg) + return pool + except Exception as e: + # Allow REST API error message to appear on UI + messages.error(request, e) + LOG.error(e) + # Redirect to failure page + redirect = reverse(self.failure_url) + return shortcuts.redirect(redirect) + + +class UpdateAddressPool(CreateAddressPool): + + id = forms.CharField(widget=forms.HiddenInput) + + network = forms.IPField( + label=_("Network Address"), + required=True, + version=forms.IPv4 | forms.IPv6, + mask=True, + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + + def handle(self, request, data): + try: + updates = {} + if 'name' in data: + updates['name'] = data['name'] + if 'order' in data: + updates['order'] = data['order'] + if data.get('ranges'): + updates['ranges'] = data['ranges'] + pool = api.sysinv.address_pool_update(request, data['id'], + **updates) + msg = (_('Address pool was successfully updated')) + messages.success(request, msg) + return pool + except Exception as e: + # Allow REST API error message to appear on UI + messages.error(request, e) + LOG.error(e) + # Redirect to failure page + redirect = reverse(self.failure_url) + return shortcuts.redirect(redirect) diff --git a/cgcs_dashboard/dashboards/admin/system_config/address_pools/tables.py b/cgcs_dashboard/dashboards/admin/system_config/address_pools/tables.py new file mode 100755 index 00000000..13296ddb --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/address_pools/tables.py @@ -0,0 +1,102 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class DeleteAddressPool(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Address Pool", + u"Delete Address Pools", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Address Pool", + u"Deleted Address Pools", + count + ) + + def get_redirect_url(self): + return reverse('horizon:admin:system_config:index') + + def delete(self, request, obj_id): + try: + api.sysinv.address_pool_delete(request, obj_id) + except Exception: + exceptions.handle(request, redirect=self.get_redirect_url()) + + +class CreateAddressPool(tables.LinkAction): + name = "create" + verbose_name = _("Create Address Pool") + url = "horizon:admin:system_config:addaddrpool" + classes = ("ajax-modal",) + icon = "plus" + + +class UpdateAddressPool(tables.LinkAction): + name = "update" + verbose_name = _("Update Address Pool") + url = "horizon:admin:system_config:updateaddrpool" + classes = ("ajax-modal", "btn-edit") + + def get_link_url(self, pool): + return reverse(self.url, args=(pool.uuid,)) + + +def get_network_column(pool): + ip_network = getattr(pool, 'network') + prefix = getattr(pool, 'prefix') + return ip_network + '/' + str(prefix) + + +def get_ranges_column(pool): + return ", ".join(['%s-%s' % (str(r[0]), str(r[1])) for r in pool.ranges]) + + +class AddressPoolsTable(tables.DataTable): + name = tables.Column("name", verbose_name=_("Name")) + network = tables.Column(get_network_column, + verbose_name=_("Network")) + order = tables.Column("order", verbose_name=_("Allocation Order")) + ranges = tables.Column(get_ranges_column, verbose_name=_("Address Ranges")) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return ("%(network)s/%(prefix)s" % + {'network': datum.network, + 'prefix': datum.prefix}) + + class Meta(object): + name = "address_pools" + verbose_name = _("Address Pools") + table_actions = (CreateAddressPool, DeleteAddressPool) + row_actions = (UpdateAddressPool, DeleteAddressPool) diff --git a/cgcs_dashboard/dashboards/admin/system_config/address_pools/views.py b/cgcs_dashboard/dashboards/admin/system_config/address_pools/views.py new file mode 100755 index 00000000..337b76fb --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/address_pools/views.py @@ -0,0 +1,81 @@ +# Copyright 2015 Wind River Systems, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import logging + +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from openstack_dashboard import api + +from openstack_dashboard.dashboards.admin.system_config.address_pools import \ + forms as address_pool_forms +from openstack_dashboard.dashboards.admin.system_config.address_pools import \ + tables as address_pool_tables + +LOG = logging.getLogger(__name__) + + +class CreateAddressPoolView(forms.ModalFormView): + form_class = address_pool_forms.CreateAddressPool + template_name = 'admin/system_config/address_pools/create.html' + success_url = 'horizon:admin:system_config:index' + failure_url = 'horizon:admin:system_config:index' + + def get_success_url(self): + return reverse(self.success_url) + + def get_failure_url(self): + return reverse(self.failure_url) + + +class UpdateAddressPoolView(forms.ModalFormView): + form_class = address_pool_forms.UpdateAddressPool + template_name = 'admin/system_config/address_pools/update.html' + success_url = 'horizon:admin:system_config:index' + failure_url = 'horizon:admin:system_config:index' + + def get_success_url(self): + return reverse(self.success_url) + + def get_failure_url(self): + return reverse(self.failure_url) + + def get_context_data(self, **kwargs): + context = super(UpdateAddressPoolView, self).get_context_data(**kwargs) + context["address_pool_uuid"] = self.kwargs['address_pool_uuid'] + return context + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + address_pool_uuid = self.kwargs['address_pool_uuid'] + try: + self._object = api.sysinv.address_pool_get( + self.request, address_pool_uuid) + except Exception: + redirect = self.success_url + msg = _('Unable to retrieve address pool details.') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_initial(self): + pool = self._get_object() + return {'id': pool.uuid, + 'name': pool.name, + 'network': pool.network + "/" + str(pool.prefix), + 'order': pool.order, + 'ranges': address_pool_tables.get_ranges_column(pool)} diff --git a/cgcs_dashboard/dashboards/admin/system_config/forms.py b/cgcs_dashboard/dashboards/admin/system_config/forms.py new file mode 100755 index 00000000..a85fed1e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/forms.py @@ -0,0 +1,938 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + + +import logging + +from collections import OrderedDict + +from cgtsclient import exc +from django.core.urlresolvers import reverse # noqa +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class UpdateSystem(forms.SelfHandlingForm): + system_uuid = forms.CharField(widget=forms.widgets.HiddenInput) + + name = forms.RegexField( + label=_("Name"), + max_length=255, + required=False, + regex=r'^[\w\.\-]+$', + error_messages={ + 'invalid': _('Name may only ' + 'contain letters, numbers, underscores, ' + 'periods and hyphens.')}) + + description = forms.CharField(label=_("Description"), + initial='location', + required=False, + help_text=_("Description of System.")) + + failure_url = 'horizon:admin:system_config:index' + + def __init__(self, request, *args, **kwargs): + super(UpdateSystem, self).__init__(request, *args, **kwargs) + + def clean(self): + cleaned_data = super(UpdateSystem, self).clean() + return cleaned_data + + def handle(self, request, data): + + try: + system_name = data['name'] + system_uuid = data['system_uuid'] + del data['system_uuid'] + + if not data['description']: + data['description'] = " " + + system = api.sysinv.system_update(request, system_uuid, **data) + msg = _('System "%s" was successfully updated.') % system_name + LOG.debug(msg) + messages.success(request, msg) + return system + except Exception: + msg = _('Failed to update system "%s".') % system_name + LOG.info(msg) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) + + +class UpdatecDNS(forms.SelfHandlingForm): + uuid = forms.CharField(widget=forms.widgets.HiddenInput) + + FIELD_LABEL_DNS_NAMESERVER_1 = _("DNS Name Server 1") + FIELD_LABEL_DNS_NAMESERVER_2 = _("DNS Name Server 2") + + NAMESERVER_1 = forms.IPField( + label=_("DNS Server 1 IP"), + required=False, + initial='NAMESERVER_1', + help_text=_("IP address of NameServer 1. " + "If you use no DNS NameServer 1, leave blank. "), + version=forms.IPv4 | forms.IPv6, + mask=False) + + NAMESERVER_2 = forms.IPField( + label=_("DNS Server 2 IP"), + required=False, + initial='NAMESERVER_2', + help_text=_("IP address of NameServer 2. " + "If you use no DNS NameServer 2, leave blank. "), + version=forms.IPv4 | forms.IPv6, + mask=False) + + NAMESERVER_3 = forms.IPField( + label=_("DNS Server 3 IP"), + required=False, + initial='NAMESERVER_3', + help_text=_("IP address of NameServer 3. " + "If you use no DNS NameServer 3, leave blank. "), + version=forms.IPv4 | forms.IPv6, + mask=False) + + failure_url = 'horizon:admin:system_config:index' + + def __init__(self, request, *args, **kwargs): + super(UpdatecDNS, self).__init__(request, *args, **kwargs) + + # def clean(self, value): + # super(UpdatecDNS, self).clean(value) + # return self.regexlist + + def handle(self, request, data): + # Here, update the maximum dns servers allowed + max_dns_servers = 3 + send_to_sysinv = False + + try: + + if data: + NAMESERVERS = OrderedDict() + + if 'uuid' in data.keys(): + if not data['uuid']: + data['uuid'] = ' ' + + else: + data['uuid'] = ' ' + + for index in range(1, max_dns_servers + 1): + if data['NAMESERVER_%s' % index] and \ + data['NAMESERVER_%s' % index] != ' ': + NAMESERVERS['NAMESERVER_%s' % index] = \ + data['NAMESERVER_%s' % index] + + dns_config = api.sysinv.dns_get(request, data['uuid']) + + if hasattr(dns_config, 'uuid'): + dns_config_uuid = dns_config.uuid + if dns_config.nameservers: + nameservers = dns_config.nameservers.split(',') + else: + nameservers = [] + + if len(NAMESERVERS) != len(nameservers): + data['action'] = 'apply' + send_to_sysinv = True + + else: + # If the value order is reversed, + # don't send action=apply + if set(nameservers) != set(NAMESERVERS.values()): + for index in range(len(nameservers)): + + if nameservers[index] != \ + NAMESERVERS['NAMESERVER_%s' % ( + index + 1)]: + data['action'] = 'apply' + send_to_sysinv = True + # we need to do action=apply only once + break + + # sysinv accepts csv values as the nameservers + data['nameservers'] = ','.join(NAMESERVERS.values()) + + else: + dns_config_uuid = ' ' + + data.pop('uuid') + + for index in range(1, max_dns_servers + 1): + data.pop('NAMESERVER_%s' % index) + + else: + dns_config_uuid = ' ' + data = {'nameservers': ' '} + + LOG.debug(data) + + if send_to_sysinv is True: + my_dns = api.sysinv.dns_update(request, + dns_config_uuid, **data) + + if my_dns: + msg = _('DNS Server was successfully updated.') + + LOG.debug(msg) + messages.success(request, msg) + + return True if my_dns else False + + else: + msg = _('No DNS Server changes have been made.') + LOG.debug(msg) + messages.info(request, msg) + return True + + except exc.ClientException as ce: + # Display REST API error on the GUI + messages.error(request, ce) + LOG.error(ce) + # redirect = reverse(self.failure_url) + return False + + except Exception: + msg = _('Failed to update DNS Server.') + messages.error(request, msg) + # msg = self.format_status_message(self.failure_message) + str(e) + LOG.info(msg) + # redirect = reverse(self.failure_url) + exceptions.handle(request, msg) + return False + + +class UpdatecNTP(forms.SelfHandlingForm): + uuid = forms.CharField(widget=forms.widgets.HiddenInput) + + NTP_SERVER_1 = forms.CharField(label=_("NTP Server 1 Address"), + initial='NTP_SERVER_1', + required=False, + help_text=_("NTP Server IP or FQDN. " + "If you use no NTP Server 1, " + "leave blank.")) + + NTP_SERVER_2 = forms.CharField(label=_("NTP Server 2 Address"), + initial='NTP_SERVER_2', + required=False, + help_text=_("NTP Server IP or FQDN." + "If you use no NTP Server 2, " + "leave blank.")) + + NTP_SERVER_3 = forms.CharField(label=_("NTP Server 3 Address"), + initial='NTP_SERVER_3', + required=False, + help_text=_("NTP Server IP or FQDN." + "If you use no NTP Server 3, " + "leave blank.")) + + failure_url = 'horizon:admin:system_config:index' + failure_message = 'Failed to update NTP configurations.' + + def __init__(self, request, *args, **kwargs): + super(UpdatecNTP, self).__init__(request, *args, **kwargs) + + def handle(self, request, data): + # Here, update the maximum ntp servers allowed + max_ntp_servers = 3 + send_to_sysinv = False + + try: + if data: + NTPSERVERS = OrderedDict() + + if 'uuid' in data.keys(): + if not data['uuid']: + data['uuid'] = ' ' + else: + data['uuid'] = ' ' + + for index in range(1, max_ntp_servers + 1): + if data['NTP_SERVER_%s' % index] and data[ + 'NTP_SERVER_%s' % index] != ' ': + NTPSERVERS['NTP_SERVER_%s' % index] = data[ + 'NTP_SERVER_%s' % index] + + ntp_config = api.sysinv.ntp_get(request, data['uuid']) + + if hasattr(ntp_config, 'uuid'): + + ntp_config_uuid = ntp_config.uuid + if ntp_config.ntpservers: + ntpservers = ntp_config.ntpservers.split(',') + else: + ntpservers = [] + + # if their sizes are different, then action=apply + if len(NTPSERVERS) != len(ntpservers): + data['action'] = 'apply' + send_to_sysinv = True + + else: + # If lengths are same, + # check if order of values have been changed + if set(ntpservers) != set(NTPSERVERS.values()): + for index in range(len(ntpservers)): + + if ntpservers[index] != NTPSERVERS[ + 'NTP_SERVER_%s' % (index + 1)]: + data['action'] = 'apply' + send_to_sysinv = True + # we need to do action=apply only once + break + + # sysinv accepts csv values as the ntpservers + data['ntpservers'] = ','.join(NTPSERVERS.values()) + + else: + ntp_config_uuid = ' ' + + data.pop('uuid') + + for index in range(1, max_ntp_servers + 1): + data.pop('NTP_SERVER_%s' % index) + + else: + ntp_config_uuid = ' ' + data = {'ntpservers': ''} + + LOG.debug(data) + + if send_to_sysinv: + my_ntp = \ + api.sysinv.ntp_update(request, ntp_config_uuid, **data) + + if my_ntp: + msg = _('NTP configuration was successfully updated. ') + LOG.debug(msg) + messages.success(request, msg) + return True + else: + return False + + else: + msg = _('No NTP Server changes have been made.') + LOG.debug(msg) + messages.info(request, msg) + return True + + except exc.ClientException as ce: + messages.error(request, ce) + # Display REST API error on the GUI + LOG.error(ce) + # redirect = reverse(self.failure_url) + return False + + except Exception: + msg = _('Failed to update NTP configuration.') + messages.error(request, msg) + LOG.info(msg) + # redirect = reverse(self.failure_url) + return False + + +class UpdatecEXT_OAM(forms.SelfHandlingForm): + uuid = forms.CharField(widget=forms.widgets.HiddenInput) + + FIELD_LABEL_EXTERNAL_OAM_SUBNET = _("External OAM Subnet") + FIELD_LABEL_EXTERNAL_OAM_GATEWAY_ADDRESS = \ + _("External OAM Gateway Address") + FIELD_LABEL_EXTERNAL_OAM_FLOATING_ADDRESS = _( + "External OAM Floating Address") + FIELD_LABEL_EXTERNAL_OAM_0_ADDRESS = _("External OAM 0 ADDRESS") + FIELD_LABEL_EXTERNAL_OAM_1_ADDRESS = _("External OAM 1 ADDRESS") + + EXTERNAL_OAM_SUBNET = forms.IPField( + label=_("External OAM Subnet"), + required=True, + initial='EXTERNAL_OAM_SUBNET', + help_text=_("Subnet address in CIDR format (e.g. 10.10.10.0/24) "), + version=forms.IPv4 | forms.IPv6, + mask=True) + + EXTERNAL_OAM_GATEWAY_ADDRESS = forms.IPField( + label=_("External OAM Gateway Address"), + required=False, + initial='EXTERNAL_OAM_GATEWAY_ADDRESS', + help_text=_("IP address of External OAM Gateay (e.g. 192.168.0.254)"), + version=forms.IPv4 | forms.IPv6, + mask=False) + + EXTERNAL_OAM_FLOATING_ADDRESS = forms.IPField( + label=_("External OAM Floating Address"), + required=True, + initial='EXTERNAL_OAM_FLOATING_ADDRESS', + help_text=_("IP address of Floating External OAM " + "(e.g. 192.168.0.254)"), + version=forms.IPv4 | forms.IPv6, + mask=False) + + EXTERNAL_OAM_0_ADDRESS = forms.IPField( + label=_("External OAM controller-0 Address"), + required=True, + initial='EXTERNAL_OAM_0_ADDRESS', + help_text=_("controller-0 External OAM IP Address" + "(e.g. 192.168.0.254)"), + version=forms.IPv4 | forms.IPv6, + mask=False) + + EXTERNAL_OAM_1_ADDRESS = forms.IPField( + label=_("External OAM controller-1 Address"), + required=True, + initial='EXTERNAL_OAM_1_ADDRESS', + help_text=_( + "controller-1 External OAM IP Address (e.g. 192.168.0.254)"), + version=forms.IPv4 | forms.IPv6, + mask=False) + + failure_url = 'horizon:admin:system_config:index' + failure_message = 'Failed to update OAM configuration.' + + def __init__(self, request, *args, **kwargs): + super(UpdatecEXT_OAM, self).__init__(request, *args, **kwargs) + + if not kwargs['initial'].get('EXTERNAL_OAM_GATEWAY_ADDRESS'): + self.fields['EXTERNAL_OAM_GATEWAY_ADDRESS'].widget = \ + forms.widgets.HiddenInput() + if api.sysinv.is_system_mode_simplex(request): + self.fields['EXTERNAL_OAM_FLOATING_ADDRESS'].label = \ + _("External OAM Address") + self.fields['EXTERNAL_OAM_FLOATING_ADDRESS'].help_text = \ + _("IP address of External OAM (e.g. 192.168.0.254)") + self.fields['EXTERNAL_OAM_0_ADDRESS'].widget = \ + forms.widgets.HiddenInput() + self.fields['EXTERNAL_OAM_1_ADDRESS'].widget = \ + forms.widgets.HiddenInput() + self.fields['EXTERNAL_OAM_0_ADDRESS'].required = False + self.fields['EXTERNAL_OAM_1_ADDRESS'].required = False + + def clean(self): + cleaned_data = super(UpdatecEXT_OAM, self).clean() + return cleaned_data + + def handle(self, request, data): + + send_to_sysinv = False + + try: + if data: + if 'uuid' in data.keys(): + if not data['uuid']: + data['uuid'] = ' ' + else: + data['uuid'] = ' ' + + oam_data = api.sysinv.extoam_get(request, data['uuid']) + + if hasattr(oam_data, 'uuid'): + oam_data_uuid = oam_data.uuid + + EXTERNAL_OAM_SUBNET = oam_data.oam_subnet + EXTERNAL_OAM_FLOATING_ADDRESS = oam_data.oam_floating_ip + EXTERNAL_OAM_GATEWAY_ADDRESS = oam_data.oam_gateway_ip + EXTERNAL_OAM_0_ADDRESS = oam_data.oam_c0_ip + EXTERNAL_OAM_1_ADDRESS = oam_data.oam_c1_ip + + data['oam_subnet'] = data['EXTERNAL_OAM_SUBNET'] + data['oam_floating_ip'] = data[ + 'EXTERNAL_OAM_FLOATING_ADDRESS'] + + if data['EXTERNAL_OAM_GATEWAY_ADDRESS']: + data['oam_gateway_ip'] = \ + data['EXTERNAL_OAM_GATEWAY_ADDRESS'] + + if not api.sysinv.is_system_mode_simplex(request): + data['oam_c0_ip'] = data['EXTERNAL_OAM_0_ADDRESS'] + data['oam_c1_ip'] = data['EXTERNAL_OAM_1_ADDRESS'] + + if EXTERNAL_OAM_SUBNET != \ + data['EXTERNAL_OAM_SUBNET'] or \ + EXTERNAL_OAM_FLOATING_ADDRESS != \ + data['EXTERNAL_OAM_FLOATING_ADDRESS'] or \ + EXTERNAL_OAM_GATEWAY_ADDRESS != \ + data['EXTERNAL_OAM_GATEWAY_ADDRESS'] or \ + EXTERNAL_OAM_0_ADDRESS != \ + data['EXTERNAL_OAM_0_ADDRESS'] or \ + EXTERNAL_OAM_1_ADDRESS != \ + data['EXTERNAL_OAM_1_ADDRESS'] != \ + oam_data.oam_c1_ip: + + data['action'] = 'apply' + send_to_sysinv = True + + else: + oam_data_uuid = ' ' + + data.pop('uuid') + data.pop('EXTERNAL_OAM_0_ADDRESS') + data.pop('EXTERNAL_OAM_1_ADDRESS') + data.pop('EXTERNAL_OAM_SUBNET') + data.pop('EXTERNAL_OAM_FLOATING_ADDRESS') + data.pop('EXTERNAL_OAM_GATEWAY_ADDRESS') + else: + oam_data_uuid = ' ' + data = {'oam_subnet': '', 'oam_floating_ip': '', + 'oam_gateway_ip': '', + 'oam_c0_ip': '', 'oam_c1_ip': ''} + + if send_to_sysinv: + LOG.info("OAM sendtosysinv data=%s", data) + + myoam_data = api.sysinv.extoam_update(self.request, + oam_data_uuid, + **data) + if myoam_data: + msg = _('OAM configuration was successfully updated. ') + + LOG.debug(msg) + messages.success(request, msg) + + return True if myoam_data else False + + else: + msg = _('No OAM configuration changes have been made.') + LOG.debug(msg) + messages.info(request, msg) + return True + + except exc.ClientException as ce: + # Display REST API error on the GUI + messages.error(request, ce) + LOG.error(ce) + # redirect = reverse(self.failure_url) + return False + + except Exception: + msg = _('Failed to update OAM configuration.') + messages.error(request, msg) + # msg = self.format_status_message(self.failure_message) + str(e) + LOG.info(msg) + # redirect = reverse(self.failure_url) + exceptions.handle(request, msg) + return False + + +class UpdateiStorage(forms.SelfHandlingForm): + uuid = forms.CharField(widget=forms.widgets.HiddenInput) + + database = forms.IntegerField( + label=_("Database Storage (GiB)"), + required=True, + help_text=_("Database storage space in gibibytes."), + min_value=0) + + cgcs = forms.IntegerField( + label=_("CGCS Storage (GiB)"), + required=True, + help_text=_("CGCS image storage space in gibibytes."), + min_value=0) + + backup = forms.IntegerField( + label=_("Backup Storage (GiB)"), + required=True, + help_text=_("Backup storage space in gibibytes."), + min_value=0) + + scratch = forms.IntegerField( + label=_("Scratch Storage (GiB)"), + required=True, + help_text=_("Platform Scratch storage space in gibibytes."), + min_value=0) + + extension = forms.IntegerField( + label=_("Extension Storage (GiB)"), + required=True, + help_text=_("Platform Extension storage space in gibibytes."), + min_value=0) + + img_conversions = forms.IntegerField( + label=_("Image Conversion Space (GiB)"), + required=False, + help_text=_("Disk space for image conversion in gibibytes."), + min_value=0) + + ceph_mon = forms.IntegerField( + label=_("Ceph Mon Storage (GiB)"), + required=False, + help_text=_("Ceph Monitor volume size in gibibytes."), + min_value=0) + + failure_url = 'horizon:admin:system_config:index' + failure_message = 'Failed to update filesystem configuration.' + + def __init__(self, request, *args, **kwargs): + super(UpdateiStorage, self).__init__(request, *args, **kwargs) + if not kwargs['initial'].get('ceph_mon'): + del self.fields['ceph_mon'] + + def clean(self): + cleaned_data = super(UpdateiStorage, self).clean() + return cleaned_data + + def handle(self, request, data): + system_uuid = data['uuid'] + data.pop('uuid') + + new_data = {k.replace("_", "-"): v for k, v in data.items()} + try: + fs_list = api.sysinv.controllerfs_list(self.request) + fs_data = {fs.name: fs.size for fs in fs_list} + + for k, v in fs_data.items(): + if new_data.get(k, None) == v: + del new_data[k] + elif k == 'ceph-mon': + ceph_data = {'ceph_mon_gib': 0} + ceph_data['ceph_mon_gib'] = new_data.get(k) + del new_data[k] + ceph_mons = api.sysinv.cephmon_list(request) + if ceph_mons: + for ceph_mon in ceph_mons: + if hasattr(ceph_mon, 'uuid'): + my_storage = api.sysinv.ceph_mon_update( + request, ceph_mon.uuid, **ceph_data) + if not my_storage: + return False + + if new_data: + my_storage = api.sysinv.storfs_update_many(request, + system_uuid, + **new_data) + return True + + except exc.ClientException as ce: + # Display REST API error on the GUI + messages.error(request, ce) + LOG.error(ce) + return False + + except Exception as e: + LOG.error('Exception with %s', e) + msg = _('Failed to update filesystem configuration.') + messages.error(request, msg) + LOG.info(msg) + exceptions.handle(request, msg) + return False + + +class UpdateiStoragePools(forms.SelfHandlingForm): + uuid = forms.CharField(widget=forms.widgets.HiddenInput) + + js_attrs = {'onchange': 'add_total_quota()', + 'onkeypress': 'this.onchange()', + 'onpaste': 'this.onchange()', + 'oninput': 'this.onchange()', + 'onload': 'this.onchange()'} + + cinder_pool_gib = forms.IntegerField( + label=_("Cinder Volumes Pool (GiB)"), + required=True, + help_text=_("Storage space allocated to cinder volumes in gibibytes."), + min_value=0, + widget=forms.NumberInput(attrs=js_attrs)) + + glance_pool_gib = forms.IntegerField( + label=_("Glance Image Pool (GiB)"), + required=True, + help_text=_("Storage space allocated to glance images in gibibytes."), + min_value=0, + widget=forms.NumberInput(attrs=js_attrs)) + + ephemeral_pool_gib = forms.IntegerField( + label=_("Ephemeral Storage Pool(Gib)"), + required=True, + help_text=_("Storage space allocated to nova ephemeral instance disks " + "in gibibytes."), + min_value=0, + widget=forms.NumberInput(attrs=js_attrs)) + + object_pool_gib = forms.IntegerField( + label=_("Object Storage Pool(Gib)"), + required=True, + help_text=_("Storage space allocated to objects in gibibytes."), + min_value=0, + widget=forms.NumberInput(attrs=js_attrs)) + + failure_url = 'horizon:admin:system_config:index' + failure_message = ('Failed to update size of ceph pools.' + ' Ceph cluster may be down, check cluster status' + ' and try again') + + def __init__(self, request, *args, **kwargs): + super(UpdateiStoragePools, self).__init__(request, *args, **kwargs) + + def clean(self): + cleaned_data = super(UpdateiStoragePools, self).clean() + return cleaned_data + + def handle(self, request, data): + + send_to_sysinv = False + + try: + + if data: + + if 'uuid' in data.keys(): + if not data['uuid']: + data['uuid'] = ' ' + + else: + data['uuid'] = ' ' + + storage_config = api.sysinv.storagepool_get(request, + data['uuid']) + data.pop('uuid') + + if hasattr(storage_config, 'uuid'): + + storage_config_uuid = storage_config.uuid + + STORAGE_VALUES = {} + + if hasattr(storage_config, 'cinder_pool_gib'): + STORAGE_VALUES['cinder_pool_gib'] = \ + unicode(storage_config._cinder_pool_gib) + + if hasattr(storage_config, 'glance_pool_gib'): + STORAGE_VALUES['glance_pool_gib'] = \ + unicode(storage_config._glance_pool_gib) + + if hasattr(storage_config, 'ephemeral_pool_gib'): + STORAGE_VALUES['ephemeral_pool_gib'] = \ + unicode(storage_config._ephemeral_pool_gib) + + if hasattr(storage_config, 'object_pool_gib'): + STORAGE_VALUES['object_pool_gib'] = \ + unicode(storage_config._object_pool_gib) + + for key in data.keys(): + data[key] = unicode(data[key]) + + LOG.info("initial send_to_sysinv=%s", send_to_sysinv) + if len(STORAGE_VALUES) != len(data): + data['action'] = 'apply' + send_to_sysinv = True + + else: + for key in STORAGE_VALUES.keys(): + if STORAGE_VALUES[key] != data[key]: + send_to_sysinv = True + break + + else: + storage_config_uuid = ' ' + + else: + storage_config_uuid = ' ' + data = {'cinder_pool_gib': '', + 'glance_pool_gib': '', + 'ephemeral_pool_gib': '', + 'object_pool_gib': ''} + + LOG.debug(data) + + if send_to_sysinv: + my_storage = api.sysinv.storpool_update(request, + storage_config_uuid, + **data) + + if my_storage: + msg = _( + 'Size of Ceph storage pools was successfully updated.') + LOG.debug(msg) + messages.success(request, msg) + return True + + return False + + else: + msg = _('Size of Ceph storage pools have not been updated.') + LOG.debug(msg) + messages.info(request, msg) + return True + + except exc.ClientException as ce: + messages.error(request, ce) + # Display REST API error on the GUI + LOG.error(ce) + return False + + except Exception: + msg = _('Failed to update sizes of Ceph storage pools.') + messages.error(request, msg) + LOG.info(msg) + exceptions.handle(request, msg) + return False + + +############################################################ +# SDN Controller Forms # +############################################################ + +SDN_TRANSPORT_MODE_CHOICES = ( + ('tcp', _("TCP")), + ('udp', _("UDP")), + ('tls', _("TLS")), +) + +SDN_ADMIN_STATE_CHOICES = ( + ('enabled', _("Administratively Enabled")), + ('disabled', _("Administratively Disabled")), +) + + +class SDNControllerForm(forms.SelfHandlingForm): + ip_address = forms.CharField(label=_("SDN Controller Host"), + required=True, + help_text=_("Provide a Fully Qualified " + "Domain Name (FQDN) or IP " + "Address value.")) + + port = forms.IntegerField(label=_("SDN Controller Port #"), + help_text=_("Remote listening port " + "on the SDN controller."), + min_value=1, + required=True) + + transport = forms.ChoiceField(label=_("SDN Control channel" + " transport mode"), + required=False, + help_text=_("The transport protocol " + "being used for the SDN " + "control channel."), + choices=SDN_TRANSPORT_MODE_CHOICES) + + state = forms.ChoiceField(label=_("SDN Controller" + " administrative state"), + required=True, + choices=SDN_ADMIN_STATE_CHOICES) + + +class CreateSDNController(SDNControllerForm): + + @classmethod + def _instantiate(cls, request, *args, **kwargs): + return cls(request, *args, **kwargs) + + def __init__(self, request, *args, **kwargs): + super(CreateSDNController, self).__init__(request, *args, **kwargs) + + # specify default values for optional choices + self.initial['transport'] = 'tcp' + self.initial['state'] = 'enabled' + + def handle(self, request, data): + try: + # Don't prevalidate. Let SysInv handle it + controller = api.sysinv.sdn_controller_create(request, **data) + msg = (_('SDN Controller was successfully created.')) + LOG.debug(msg) + messages.success(request, msg) + return controller + except Exception: + redirect = reverse('horizon:admin:system_config:index') + msg = _('Failed to create SDN Controller.') + exceptions.handle(request, msg, redirect=redirect) + + +class UpdateSDNController(SDNControllerForm): + uuid = forms.CharField(widget=forms.HiddenInput) + failure_url = 'horizon:admin:system_config:index' + + def handle(self, request, data): + try: + controller = api.sysinv.sdn_controller_update( + request, **data) + msg = (_('SDN Controller %s was successfully updated.') % + data['uuid']) + LOG.debug(msg) + messages.success(request, msg) + return controller + except Exception: + msg = (_('Failed to update SDN Controller %s') % + data['uuid']) + LOG.info(msg) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) + + +class EditPipeline(forms.SelfHandlingForm): + pipeline_name = forms.CharField(label=_("Name"), + initial='name', + required=False, + help_text=_("Name of the Pipeline."), + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + + location = forms.CharField(label=_("Location"), + initial='location', + required=False, + help_text=_("Location of PM file.")) + + enabled = forms.BooleanField(label=_("Enabled"), + initial='enabled', + required=False, + help_text=_( + "Enable or disable writing to file")) + + max_bytes = forms.IntegerField(initial='max_bytes', + label=_('Max Bytes'), + help_text=_( + 'Maximum size (in bytes) to allow the' + ' file to grow before backing it up.'), + required=False) + + backup_count = forms.IntegerField(initial='backup_count', + label=_('Backup Count'), + help_text=_('Number of files to keep.'), + required=False) + + compress = forms.BooleanField(label=_("Compress Backups"), + initial='compress', + required=False, + help_text=_("Gzip compress the" + " backup files")) + + failure_url = 'horizon:admin:system_config:index' + + def __init__(self, request, *args, **kwargs): + super(EditPipeline, self).__init__(request, *args, **kwargs) + + def handle(self, request, data): + pipeline_name = data['pipeline_name'] + try: + # Remove entries from this update that can not be updated + del data['pipeline_name'] + + # Update the pipeline using the API call + pipeline = api.ceilometer.pipeline_update(request, pipeline_name, + data) + + msg = _('Pipeline "%s" was successfully updated.') % pipeline_name + LOG.debug(msg) + messages.success(request, msg) + return pipeline + + except Exception as ex: + msg = _('Failed to update pipeline "%s".') % pipeline_name + LOG.info(msg) + redirect = reverse(self.failure_url) + exceptions.handle(request, str(ex), redirect=redirect) diff --git a/cgcs_dashboard/dashboards/admin/system_config/panel.py b/cgcs_dashboard/dashboards/admin/system_config/panel.py new file mode 100755 index 00000000..4b64d1af --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/panel.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2013-2014 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.api import base +from openstack_dashboard.dashboards.admin import dashboard + + +class SystemConfig(horizon.Panel): + name = _("System Configuration") + slug = "system_config" + permissions = ('openstack.services.platform',) + + def allowed(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return super(SystemConfig, self).allowed(context) + + def nav(self, context): + if not base.is_service_enabled(context['request'], 'platform'): + return False + else: + return True + + +dashboard.Admin.register(SystemConfig) diff --git a/cgcs_dashboard/dashboards/admin/system_config/tables.py b/cgcs_dashboard/dashboards/admin/system_config/tables.py new file mode 100644 index 00000000..828d6111 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/tables.py @@ -0,0 +1,440 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +import logging + +from cgtsclient import exc +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import tables +from openstack_dashboard import api + + +LOG = logging.getLogger(__name__) + +CONFIG_STATE_CREATED = 'created' +CONFIG_STATE_MODIFIED = 'modified' +CONFIG_STATE_APPLYING = 'applying' +CONFIG_STATE_APPLIED = 'applied' +CONFIG_STATE_FAILED = 'failed' +CONFIG_STATE_ABORTED = 'aborted' + +STATE_DISPLAY_CHOICES = ( + (CONFIG_STATE_CREATED, _("Created")), + (CONFIG_STATE_MODIFIED, _("Modified")), + (CONFIG_STATE_APPLYING, _("Applying")), + (CONFIG_STATE_APPLIED, _("Applied")), + (CONFIG_STATE_FAILED, _("Failed")), + (CONFIG_STATE_ABORTED, _("Aborted")), +) + + +def get_short_software_version(system): + return system.get_short_software_version() + + +class EditSystem(tables.LinkAction): + name = "update" + verbose_name = _("Edit System") + url = "horizon:admin:system_config:update_system" + classes = ("ajax-modal", "btn-edit") + + +class SystemsTable(tables.DataTable): + system_name = tables.Column('name', + verbose_name=_('Name')) + + system_type = tables.Column('system_type', + verbose_name=_('System Type')) + + system_mode = tables.Column('system_mode', + verbose_name=_('System Mode')) + + system_desc = tables.Column('description', + verbose_name=_("Description")) + + system_version = tables.Column(get_short_software_version, + verbose_name=_("Version")) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.name + + class Meta(object): + name = "systems" + verbose_name = _("Systems") + multi_select = False + row_actions = (EditSystem, ) + + +class EditcDNS(tables.LinkAction): + name = "update_cdns" + verbose_name = _("Edit DNS") + url = "horizon:admin:system_config:update_cdns_table" + classes = ("ajax-modal", "btn-edit") + + +class EditcNTP(tables.LinkAction): + name = "update_cntp" + verbose_name = _("Edit NTP") + url = "horizon:admin:system_config:update_cntp_table" + classes = ("ajax-modal", "btn-edit") + + +class EditcOAM(tables.LinkAction): + name = "update_coam" + verbose_name = _("Edit OAM IP") + url = "horizon:admin:system_config:update_coam_table" + classes = ("ajax-modal", "btn-edit") + + +class EditiStorage(tables.LinkAction): + name = "update_storage" + verbose_name = _("Edit Filesystem") + url = "horizon:admin:system_config:update_storage_table" + classes = ("ajax-modal", "btn-edit") + + +class EditiStoragePools(tables.LinkAction): + name = "update_storage_pools" + verbose_name = _("Edit size of Ceph storage pools") + url = "horizon:admin:system_config:update_storage_pools_table" + classes = ("ajax-modal", "btn-edit") + + +class UpdateDNSRow(tables.Row): + ajax = True + + def get_data(self, request, obj_id): + return api.sysinv.dns_get(request, obj_id) + + +class cDNSTable(tables.DataTable): + nameserver_1 = tables.Column( + "nameserver_1", + verbose_name=_('DNS Server 1 IP')) + + nameserver_2 = tables.Column( + "nameserver_2", + verbose_name=_('DNS Server 2 IP')) + + nameserver_3 = tables.Column( + "nameserver_3", + verbose_name=_('DNS Server 3 IP')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.uuid + # return datum.cname + + class Meta(object): + name = "cdns_table" + verbose_name = _("DNS") + row_class = UpdateDNSRow + multi_select = False + table_actions = (EditcDNS,) + + +class UpdateNTPRow(tables.Row): + ajax = True + + def get_data(self, request, obj_id): + return api.sysinv.ntp_get(request, obj_id) + + +class cNTPTable(tables.DataTable): + ntpserver_1 = tables.Column( + 'ntpserver_1', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('NTP Server 1 Address')) + + ntpserver_2 = tables.Column( + 'ntpserver_2', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('NTP Server 2 Address')) + + ntpserver_3 = tables.Column( + 'ntpserver_3', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('NTP Server 3 Address')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.uuid + + class Meta(object): + name = "cntp_table" + verbose_name = _("NTP") + row_class = UpdateNTPRow + multi_select = False + table_actions = (EditcNTP,) + + +class UpdateOAMRow(tables.Row): + ajax = True + + def get_data(self, request, obj_id): + return api.sysinv.extoam_get(request, obj_id) + + +class cOAMTable(tables.DataTable): + region_config = tables.Column( + 'region_config', + verbose_name=_('OAM Region Config'), + hidden=True) + + oam_subnet = tables.Column( + 'oam_subnet', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('OAM Subnet')) + + oam_floating_ip = tables.Column( + 'oam_floating_ip', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('OAM Floating IP')) + + oam_gateway_ip = tables.Column( + 'oam_gateway_ip', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('OAM Gateway IP')) + + oam_c0_ip = tables.Column( + 'oam_c0_ip', + # link="horizon:admin:system_config:detail_cdns", + verbose_name=_('OAM controller-0 IP')) + + oam_c1_ip = tables.Column( + 'oam_c1_ip', + verbose_name=_('OAM controller-1 IP')) + + # This is optional for non region config + oam_start_ip = tables.Column( + 'oam_start_ip', + # hidden=(region_config.transform.strip() == "True"), + hidden=True, + verbose_name=_('OAM Start IP')) + + oam_end_ip = tables.Column( + 'oam_end_ip', + # hidden=(region_config.transform.strip() != "True"), + hidden=True, + verbose_name=_('OAM End IP')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.uuid + + def __init__(self, request, *args, **kwargs): + super(cOAMTable, self).__init__(request, *args, **kwargs) + + if api.sysinv.is_system_mode_simplex(request): + self.columns['oam_floating_ip'].verbose_name = _('OAM IP') + del self.columns['oam_c0_ip'] + del self.columns['oam_c1_ip'] + + class Meta(object): + name = "coam_table" + verbose_name = _("OAM IP") + row_class = UpdateOAMRow + multi_select = False + table_actions = (EditcOAM,) + + +class UpdateStorageRow(tables.Row): + ajax = True + + def get_data(self, request): + return api.sysinv.storagefs_list(request) + + +class iStorageTable(tables.DataTable): + name = tables.Column( + 'name', + verbose_name=_('Storage Name')) + + size = tables.Column( + 'size', + verbose_name=_('Size (GiB)')) + + def get_object_id(self, datum): + return unicode(datum.name) + + def get_object_display(self, datum): + return + + def __init__(self, request, *args, **kwargs): + super(iStorageTable, self).__init__(request, *args, **kwargs) + + class Meta(object): + name = "storage_table" + verbose_name = _("Controller Filesystem") + multi_select = False + table_actions = (EditiStorage,) + columns = ('name', 'size') + + +class iStoragePoolsTable(tables.DataTable): + cinder_pool_gib = tables.Column( + 'cinder_pool_gib', + verbose_name=_('Cinder Volume Storage (GiB)')) + + glance_pool_gib = tables.Column( + 'glance_pool_gib', + verbose_name=_('Glance Image Storage (GiB)')) + + ephemeral_pool_gib = tables.Column( + 'ephemeral_pool_gib', + verbose_name=_('Nova Ephemeral Disk Storage (GiB)')) + + object_pool_gib = tables.Column( + 'object_pool_gib', + verbose_name=_('Object Storage (GiB)')) + + total_ceph_space = tables.Column( + 'ceph_total_space_gib', + verbose_name=_('Ceph total space (GiB)')) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.uuid + + class Meta(object): + name = "storage_pools_table" + verbose_name = _("Ceph Storage Pools") + row_class = UpdateStorageRow + multi_select = False + table_actions = (EditiStoragePools,) + + +########################################################### +# SDN Controller Tables # +########################################################### +class DeleteSDNController(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete SDN Controller", + u"Delete SDN Controllers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted SDN Controller", + u"Deleted SDN Controllers", + count + ) + + def delete(self, request, obj_id): + try: + api.sysinv.sdn_controller_delete(request, obj_id) + except exc.ClientException as ce: + # Display REST API error on the GUI + LOG.error(ce) + msg = self.failure_message + " " + str(ce) + self.failure_message = msg + return False + except Exception as e: + msg = self.format_status_message(self.failure_message) + str(e) + e.handle(request, msg) + return False + + +class CreateSDNController(tables.LinkAction): + name = "create" + verbose_name = _("Create SDN Controller") + url = "horizon:admin:system_config:create_sdn_controller_table" + classes = ("ajax-modal", "btn-create") + + +class EditSDNController(tables.LinkAction): + name = "update" + verbose_name = _("Edit SDN Controller") + url = "horizon:admin:system_config:update_sdn_controller_table" + classes = ("ajax-modal", "btn-edit") + + +class SDNControllerFilterAction(tables.FilterAction): + def filter(self, table, controllers, filter_string): + """Naive case-insensitive search.""" + s = filter_string.lower() + return [controller for controller in controllers + if s in controller.name.lower()] + + +class SDNControllerTable(tables.DataTable): + name = tables.Column("uuid", verbose_name=_("Name"), + link="horizon:admin:system_config:" + "detail_sdn_controller_table") + state = tables.Column("state", + verbose_name=_("Administrative State")) + ip_address = tables.Column("ip_address", + verbose_name=_("Host")) + port = tables.Column("port", + verbose_name=_("Remote Port")) + + def get_object_id(self, datum): + return unicode(datum.uuid) + + def get_object_display(self, datum): + return datum.uuid + + class Meta(object): + name = "sdn_controller_table" + verbose_name = _("SDN Controllers") + table_actions = (CreateSDNController, DeleteSDNController, + SDNControllerFilterAction) + row_actions = (EditSDNController, DeleteSDNController) + + +########################################################### +# Pipeline # +########################################################### +class ChangeCeilometerPipeline(tables.LinkAction): + name = "update_defaults" + verbose_name = _("Update Settings") + url = "horizon:admin:system_config:update" + classes = ("ajax-modal", "btn-edit") + + # def get_link_url(self, pipeline): + # step = 'update_group_members' + # #base_url = reverse(self.url, args=[name]) + # param = urlencode({"step": step}) + # return "?".join([base_url, param]) + + +class CeilometerPipelinesTable(tables.DataTable): + name = tables.Column("name", verbose_name=_('Name')) + location = tables.Column("location", verbose_name=_('Location')) + max_bytes = tables.Column('max_bytes', verbose_name=_('Max Bytes')) + backup_count = tables.Column('backup_count', + verbose_name=_('Backup Count')) + compress = tables.Column('compress', verbose_name=_('Compress')) + enabled = tables.Column("enabled", verbose_name=_('Enabled')) + + def get_object_id(self, obj): + return "%s" % (obj.name,) + + class Meta(object): + name = "ceilometer_pipelines" + verbose_name = _("Pipelines") + row_actions = (ChangeCeilometerPipeline, ) + multi_select = False diff --git a/cgcs_dashboard/dashboards/admin/system_config/tabs.py b/cgcs_dashboard/dashboards/admin/system_config/tabs.py new file mode 100755 index 00000000..23fe3349 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/tabs.py @@ -0,0 +1,260 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + + +import logging + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs + +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.system_config.address_pools import \ + tables as address_pool_tables +from openstack_dashboard.dashboards.admin.system_config \ + import tables as toplevel_tables + + +LOG = logging.getLogger(__name__) + + +class SystemsTab(tabs.TableTab): + table_classes = (toplevel_tables.SystemsTable, ) + name = _("Systems") + slug = "systems" + template_name = ("admin/system_config/_systems.html") + + def get_systems_data(self): + request = self.request + systems = [] + try: + systems = api.sysinv.system_list(request) + except Exception: + exceptions.handle(request, + _('Unable to retrieve systems list.')) + return systems + + +class AddressPoolsTab(tabs.TableTab): + table_classes = (address_pool_tables.AddressPoolsTable,) + name = _("Address Pools") + slug = "address_pools" + template_name = ("horizon/common/_detail_table.html") + + def get_address_pools_data(self): + request = self.request + pools = [] + try: + pools = api.sysinv.address_pool_list(request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve address pool list.')) + + return pools + + +class cDNSTab(tabs.TableTab): + table_classes = (toplevel_tables.cDNSTable, ) + name = _("DNS") + slug = "cdns_table" + template_name = ("admin/system_config/_cdns_table.html") + + def get_cdns_table_data(self): + request = self.request + data = [] + + try: + dns_data = {'uuid': ' ', + 'nameserver_1': ' ', + 'nameserver_2': ' ', + 'nameserver_3': ' '} + + dns_list = api.sysinv.dns_list(request) + if dns_list: + dns = dns_list[0] + + dns_data['uuid'] = dns.uuid + if dns.nameservers: + servers = dns.nameservers.split(",") + for index, server in enumerate(servers): + dns_data['nameserver_%s' % (index + 1)] = server + + data.append(type('DNS', (object,), dns_data)()) + + except Exception: + exceptions.handle(request, + _('Unable to retrieve dns list.')) + + return data + + +class cNTPTab(tabs.TableTab): + table_classes = (toplevel_tables.cNTPTable, ) + name = _("NTP") + slug = "cntp_table" + template_name = ("admin/system_config/_cntp_table.html") + + def get_cntp_table_data(self): + request = self.request + data = [] + + try: + ntp_data = {'uuid': ' ', + 'ntpserver_1': ' ', + 'ntpserver_2': ' ', + 'ntpserver_3': ' '} + + ntp_list = api.sysinv.ntp_list(request) + if ntp_list: + ntp = ntp_list[0] + + ntp_data['uuid'] = ntp.uuid + if ntp.ntpservers: + servers = ntp.ntpservers.split(",") + for index, server in enumerate(servers): + ntp_data['ntpserver_%s' % (index + 1)] = server + + data.append(type('NTP', (object,), ntp_data)()) + + except Exception: + exceptions.handle(request, + _('Unable to retrieve ntp list.')) + + return data + + +class cEXTOAMTab(tabs.TableTab): + table_classes = (toplevel_tables.cOAMTable, ) + name = _("OAM IP") + slug = "coam_able" + template_name = ("admin/system_config/_coam_table.html") + + def get_coam_table_data(self): + request = self.request + oam_list = [] + + try: + oam_list = api.sysinv.extoam_list(request) + + except Exception: + exceptions.handle(request, + _('Unable to retrieve oam list.')) + # Sort hosts by hostname + # hosts.sort(key=lambda f: (f.personality, f.hostname)) + return oam_list + + +class iStorageTab(tabs.TableTab): + table_classes = (toplevel_tables.iStorageTable, ) + name = _("Controller Filesystem") + slug = "storage_table" + template_name = ("admin/system_config/_storage_table.html") + + def get_storage_table_data(self): + request = self.request + storage_list = [] + + try: + storage_list = api.sysinv.controllerfs_list(request) + + except Exception: + exceptions.handle(request, + _('Unable to retrieve filesystem list.')) + + return storage_list + + +class iStoragePoolsTab(tabs.TableTab): + table_classes = (toplevel_tables.iStoragePoolsTable, ) + name = _("Ceph Storage Pools") + slug = "storage_pools_table" + template_name = ("admin/system_config/_storage_pools_table.html") + + def get_storage_pools_table_data(self): + request = self.request + storage_list = [] + + try: + storage_list = api.sysinv.storagepool_list(request) + + except Exception: + exceptions.handle(request, + _('Unable to retrieve storage pool list.')) + + return storage_list + + def allowed(self, request): + """Only display the Tab if we have a ceph based setup""" + try: + cinder_backend = api.sysinv.get_cinder_backend(request) + if api.sysinv.CINDER_BACKEND_CEPH in cinder_backend: + return True + except Exception: + pass + return False + + +class SDNControllerTab(tabs.TableTab): + table_classes = (toplevel_tables.SDNControllerTable, ) + name = _("SDN Controllers") + slug = "sdn_controller_table" + template_name = ("admin/system_config/_sdn_controller_table.html") + + def get_sdn_controller_table_data(self): + request = self.request + controllers = [] + + try: + controllers = api.sysinv.sdn_controller_list(request) + + except Exception: + exceptions.handle(request, + _('Unable to retrieve SDN Controller list.')) + + return controllers + + def allowed(self, request): + """Only display the Tab if we have a SDN based setup""" + try: + sdn_enabled = api.sysinv.get_sdn_enabled(request) + return sdn_enabled + except Exception: + return False + + +class CeilometerConfigTab(tabs.TableTab): + table_classes = (toplevel_tables.CeilometerPipelinesTable,) + name = _("Pipelines") + slug = "ceilometer_config" + template_name = ("horizon/common/_detail_table.html") + + def get_ceilometer_pipelines_data(self): + request = self.tab_group.request + pipelines = [] + try: + pipelines = api.ceilometer.pipeline_list(request) + except Exception: + msg = _('Unable to retrieve ceilometer pipeline data.') + exceptions.handle(request, msg) + return pipelines + + def allowed(self, request): + if request.user.services_region == 'SystemController': + return False + return api.base.is_TiS_region(request) + + +class ConfigTabs(tabs.TabGroup): + slug = "system_config_tab" + tabs = (SystemsTab, AddressPoolsTab, cDNSTab, cNTPTab, cEXTOAMTab, + iStorageTab, iStoragePoolsTab, SDNControllerTab, + CeilometerConfigTab) + sticky = True diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cdns_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cdns_table.html new file mode 100755 index 00000000..9a06f771 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cdns_table.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ cdns_table_table.render }} +
    +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cntp_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cntp_table.html new file mode 100755 index 00000000..46aa9f48 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_cntp_table.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ cntp_table_table.render }} +
    +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_coam_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_coam_table.html new file mode 100755 index 00000000..52e88358 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_coam_table.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ coam_table_table.render }} +
    +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_create_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_create_sdn_controller_table.html new file mode 100755 index 00000000..24593c62 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_create_sdn_controller_table.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_sdn_controller_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:create_sdn_controller_table' %} +{% endblock %} + +{% block modal-header %}{% trans "Create an SDN Controller" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create and register a remote Software Defined Networking (SDN) Controller."%}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_detail_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_detail_sdn_controller_table.html new file mode 100644 index 00000000..27de1440 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_detail_sdn_controller_table.html @@ -0,0 +1,18 @@ +{% load i18n sizeformat %} + +

    {% trans "SDN Controller Overview" %}

    + +
    +
    +
    {% trans "Name" %}
    +
    {{ sdn_controller_table.uuid }}
    +
    {% trans "Host" %}
    +
    {{ sdn_controller_table.ip_address }}
    +
    {% trans "Port Number" %}
    +
    {{ sdn_controller_table.port }}
    +
    {% trans "Transport Mode" %}
    +
    {{ sdn_controller_table.tranport|default:"TCP" }}
    +
    {% trans "Administrative State" %}
    +
    {{ sdn_controller_table.state|default:"Administratively Enabled" }}
    +
    +
    diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_edit.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_edit.html new file mode 100755 index 00000000..f6cedec3 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_edit.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_pipeline_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update' pipeline_name%}{% endblock %} + +{% block modal_id %}edit_pipeline_modal{% endblock %} +{% block modal-header %}{% trans "Edit Pipeline" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the current pipeline." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_sdn_controller_table.html new file mode 100644 index 00000000..9ed1455a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_sdn_controller_table.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ sdn_controller_table_table.render }} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_pools_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_pools_table.html new file mode 100755 index 00000000..3da24ccf --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_pools_table.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ storage_pools_table_table.render }} +
    +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_table.html new file mode 100755 index 00000000..1a483ed8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_storage_table.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ storage_table_table.render }} +
    +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_systems.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_systems.html new file mode 100755 index 00000000..6979a7d4 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_systems.html @@ -0,0 +1,7 @@ +{% load i18n sizeformat %} + +{% block main %} +
    + {{ systems_table.render }} +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cdns_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cdns_table.html new file mode 100755 index 00000000..084e7712 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cdns_table.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_iconfig_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_cdns_table' %}{% endblock %} + +{% block modal_id %}edit_iconfig_modal{% endblock %} +{% block modal-header %}{% trans "Edit DNS" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the DNS nameservers." %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cntp_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cntp_table.html new file mode 100755 index 00000000..c0823497 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_cntp_table.html @@ -0,0 +1,30 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_iconfig_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_cntp_table' %}{% endblock %} + +{% block modal_id %}edit_iconfig_modal{% endblock %} +{% block modal-header %}{% trans "Edit NTP" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the NTP servers." %}

    +

    {% trans "WARNING: Completion of NTP configuration change will require lock and unlock of affected hosts." %}

    +

    {% trans "Major Alarms will be raised against the affected hosts until the lock unlock operation is successfully completed." %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_coam_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_coam_table.html new file mode 100755 index 00000000..ac53f5b8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_coam_table.html @@ -0,0 +1,31 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_iconfig_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_coam_table' %}{% endblock %} + +{% block modal_id %}edit_iconfig_modal{% endblock %} +{% block modal-header %}{% trans "Edit OAM IP" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of OAM External IP." %}

    +

    {% trans "WARNING: Completion of OAM configuration will take a few minutes." %}

    +

    {% trans "In that time, major alarms will be raised and then eventually cleared as affected services receive the new OAM settings. If the Floating IP has changed, you will have to re-login to this web application using the new Floating IP." %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_pools_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_pools_table.html new file mode 100755 index 00000000..9fd6bc5c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_pools_table.html @@ -0,0 +1,55 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_iconfig_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_storage_pools_table' %}{% endblock %} + +{% block modal_id %}edit_iconfig_modal{% endblock %} +{% block modal-header %}{% trans "Edit size of Ceph Storage Pools" %}{% endblock %} + +{% block modal-body %} + + +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the quota allocated to the pools of the Ceph storage cluster." %}

    +

    {% trans "A quota value of 0 will allow the storage associated with that pool to consume all available space in the Ceph cluster." %}

    +

    {% trans "The sum of the desired quotas must equal 100% of the cluster size." %}

    +

    {{ configured_quota }} GiB out of {{ cluster_total }} GiB configured

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_table.html new file mode 100755 index 00000000..daa45e4e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_istorage_table.html @@ -0,0 +1,52 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_iconfig_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_storage_table' %}{% endblock %} + +{% block modal_id %}edit_iconfig_modal{% endblock %} +{% block modal-header %}{% trans "Edit Controller Filesystem" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    + +{% if is_system_mode_simplex %} +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the Filesystem." %}

    +

    {% trans "WARNING: Filesystem sizes can not be decreased after configuration operation. " %}

    +

    {% trans "Major Alarms will be raised against the affected hosts until the configuration operation is successfully completed." %}

    +
    +{% else %} +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the Filesystem." %}

    +

    {% trans "WARNING: Filesystem sizes can not be decreased after configuration operation. " %}

    +

    {% trans "Major Alarms will be raised against the affected hosts until the configuration operation is successfully completed." %}

    +

    {% trans "For the Ceph Storage size configuration operation: the affected hosts will need to be 'host-reinstall' and/or disk replacement may be required to be performed to accomodate the increased filesystem sizes. The host firmware should be configured to boot from network (management network)." %}

    +
    +{% endif %} +{% endblock %} + +{% block modal-footer %} +{% if is_system_mode_simplex %} + {% trans "Cancel" %} + +{% else %} + {% trans "Cancel" %} + +{% endif %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_sdn_controller_table.html new file mode 100755 index 00000000..6305b1bc --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_sdn_controller_table.html @@ -0,0 +1,29 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_sdn_controller_table_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_sdn_controller_table' uuid %}{% endblock %} + +{% block modal-header %}{% trans "Edit SDN Controller" %}{% endblock %} + +{% block modal-body %} +
    +
    +
    {% trans "UUID" %}
    +
    {{ uuid }}
    +
    +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    {% trans "You may update the editable properties of the SDN Controller here." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_system.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_system.html new file mode 100755 index 00000000..c123469c --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/_update_system.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}edit_system_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:update_system' system_id %}{% endblock %} + +{% block modal_id %}edit_system_modal{% endblock %} +{% block modal-header %}{% trans "Edit System" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can update the configuration of the system." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_create.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_create.html new file mode 100755 index 00000000..ad964e57 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_create.html @@ -0,0 +1,25 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_address_pool_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:addaddrpool' %} +{% endblock %} + +{% block modal-header %}{% trans "Create Address Pool" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description" %}:

    +

    {% trans "You can create an IP address pool which can later be associated to an interface object." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_update.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_update.html new file mode 100755 index 00000000..bfe29e8e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/_update.html @@ -0,0 +1,29 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}update_address_pool_form{% endblock %} +{% block form_action %}{% url 'horizon:admin:system_config:updateaddrpool' address_pool_uuid %}{% endblock %} + +{% block modal-header %}{% trans "Update Address Pool" %}{% endblock %} + +{% block modal-body %} +
    +
    +
    {% trans "ID" %}
    +
    {{ address_pool_uuid }}
    +
    +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    +
    +

    {% trans "Description:" %}

    +

    {% trans "You may update the editable properties of your address pool here." %}

    +
    +{% endblock %} + +{% block modal-footer %} + Cancel + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/create.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/create.html new file mode 100755 index 00000000..411ffcec --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Address Pool" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Address Pool") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/address_pools/_create.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/update.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/update.html new file mode 100755 index 00000000..dc1cc5b5 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/address_pools/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Address Pool" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Address Pool") %} +{% endblock page_header %} + +{% block main %} + {% include 'admin/system_config/address_pools/_update.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/create_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/create_sdn_controller_table.html new file mode 100644 index 00000000..33d8a306 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/create_sdn_controller_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create an SDN Controller" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create an SDN Controller") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_create_sdn_controller_table.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/detail_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/detail_sdn_controller_table.html new file mode 100644 index 00000000..6a43557e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/detail_sdn_controller_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "SDN Controller Detail"%}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("SDN Controller Detail: ")|add:sdn_controller_table.name %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_detail_sdn_controller_table.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/edit.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/edit.html new file mode 100755 index 00000000..15848090 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/edit.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Pipeline" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Pipeline") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_edit.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/index.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/index.html new file mode 100755 index 00000000..47253b2d --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/index.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "System Config" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("System Configuration") %} +{% endblock page_header %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} + diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cdns_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cdns_table.html new file mode 100755 index 00000000..9525a255 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cdns_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit DNS" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit DNS") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_update_cdns_table.html" %} +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cntp_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cntp_table.html new file mode 100755 index 00000000..db803571 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_cntp_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit NTP" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit NTP") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_update_cntp_table.html" %} +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_coam_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_coam_table.html new file mode 100755 index 00000000..f47155c9 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_coam_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit OAM IP" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit OAM IP") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_update_coam_table.html" %} +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_pools_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_pools_table.html new file mode 100755 index 00000000..eed1d113 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_pools_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit size of Ceph Storage Pools" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit size of Ceph Storage Pools") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_update_istorage_pools_table.html" %} +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_table.html new file mode 100755 index 00000000..23da8f1b --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_istorage_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit Controller Filesystem" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit Filesystem") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_update_istorage_table.html" %} +{% endblock %} \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_sdn_controller_table.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_sdn_controller_table.html new file mode 100644 index 00000000..7dac90c8 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_sdn_controller_table.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update SDN Controller" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update SDN Controller") %} +{% endblock page_header %} + +{% block main %} + {% include 'admin/system_config/_update_sdn_controller_table.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_system.html b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_system.html new file mode 100755 index 00000000..732af58e --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/templates/system_config/update_system.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit System" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Edit System") %} +{% endblock page_header %} + +{% block main %} + {% include "admin/system_config/_update_system.html" %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/admin/system_config/urls.py b/cgcs_dashboard/dashboards/admin/system_config/urls.py new file mode 100755 index 00000000..3a9a041a --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/urls.py @@ -0,0 +1,75 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +from django.conf.urls import url + +from openstack_dashboard.dashboards.admin.system_config.address_pools import \ + views as address_pool_views +from openstack_dashboard.dashboards.admin.system_config.views import \ + CreateSDNControllerView +from openstack_dashboard.dashboards.admin.system_config.views import \ + DetailSDNControllerView +from openstack_dashboard.dashboards.admin.system_config.views \ + import IndexView +from openstack_dashboard.dashboards.admin.system_config.views \ + import UpdatecDNSView +from openstack_dashboard.dashboards.admin.system_config.views \ + import UpdatecEXT_OAMView +from openstack_dashboard.dashboards.admin.system_config.views \ + import UpdatecNTPView +from openstack_dashboard.dashboards.admin.system_config.views import \ + UpdateiStoragePoolsView +from openstack_dashboard.dashboards.admin.system_config.views import \ + UpdateiStorageView +from openstack_dashboard.dashboards.admin.system_config.views import \ + UpdatePipelineView +from openstack_dashboard.dashboards.admin.system_config.views import \ + UpdateSDNControllerView +from openstack_dashboard.dashboards.admin.system_config.views import \ + UpdateSystemView + +UUID = r'^(?P[^/]+)/%s$' +PIPELINE = r'^(?P[^/]+)/%s$' +urlpatterns = [ + url(r'^$', IndexView.as_view(), name='index'), + + url(r'^(?P[^/]+)/update_system/$', + UpdateSystemView.as_view(), name='update_system'), + + url(r'^addaddrpool/$', + address_pool_views.CreateAddressPoolView.as_view(), + name='addaddrpool'), + url(r'^(?P[^/]+)/updateaddrpool/$', + address_pool_views.UpdateAddressPoolView.as_view(), + name='updateaddrpool'), + + url(r'^update_cdns_table/$', UpdatecDNSView.as_view(), + name='update_cdns_table'), + url(r'^update_cntp_table/$', UpdatecNTPView.as_view(), + name='update_cntp_table'), + url(r'^update_coam_table/$', UpdatecEXT_OAMView.as_view(), + name='update_coam_table'), + + url(r'^update_istorage_table/$', UpdateiStorageView.as_view(), + name='update_storage_table'), + + url(r'^update_istorage_pools_table/$', UpdateiStoragePoolsView.as_view(), + name='update_storage_pools_table'), + url(UUID % 'update_sdn_controller_table', + UpdateSDNControllerView.as_view(), + name='update_sdn_controller_table'), + url(UUID % 'detail_sdn_controller_table', + DetailSDNControllerView.as_view(), + name='detail_sdn_controller_table'), + url(r'^create_sdn_controller_table/$', + CreateSDNControllerView.as_view(), + name='create_sdn_controller_table'), + + url(PIPELINE % 'update', UpdatePipelineView.as_view(), + name='update') +] diff --git a/cgcs_dashboard/dashboards/admin/system_config/views.py b/cgcs_dashboard/dashboards/admin/system_config/views.py new file mode 100755 index 00000000..a38f5754 --- /dev/null +++ b/cgcs_dashboard/dashboards/admin/system_config/views.py @@ -0,0 +1,445 @@ +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon import tabs + +from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.system_config.forms \ + import CreateSDNController +from openstack_dashboard.dashboards.admin.system_config.forms \ + import EditPipeline +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdatecDNS +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdatecEXT_OAM +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdatecNTP +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdateiStorage +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdateiStoragePools +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdateSDNController +from openstack_dashboard.dashboards.admin.system_config.forms \ + import UpdateSystem +from openstack_dashboard.dashboards.admin.system_config.tables \ + import SDNControllerTable +from openstack_dashboard.dashboards.admin.system_config.tabs \ + import ConfigTabs + + +LOG = logging.getLogger(__name__) + + +class IndexView(tabs.TabbedTableView): + tab_group_class = ConfigTabs + template_name = 'admin/system_config/index.html' + page_title = _("System Configuration") + + def get_tabs(self, request, *args, **kwargs): + return self.tab_group_class(request, **kwargs) + + +class UpdateSystemView(forms.ModalFormView): + form_class = UpdateSystem + template_name = 'admin/system_config/update_system.html' + context_object_name = 'system' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + context = super(UpdateSystemView, self).get_context_data(**kwargs) + context['system_id'] = self.kwargs['system_id'] + return context + + def get_initial(self): + try: + system = api.sysinv.system_get(self.request) + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve system data.")) + + return {'system_uuid': system.uuid, + 'name': system.name, + 'description': system.description} + + +class UpdatecDNSView(forms.ModalFormView): + form_class = UpdatecDNS + template_name = 'admin/system_config/update_cdns_table.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + context = super(UpdatecDNSView, self).get_context_data(**kwargs) + dns_list = api.sysinv.dns_list(self.request) + + if dns_list: + if "uuid" in dns_list[0]._attrs: + uuid = dns_list[0].uuid + + else: + uuid = " " + + else: + uuid = " " + + context['uuid'] = uuid + return context + + def get_initial(self): + # request = self.request + dns_form_data = {'uuid': ' ', + 'NAMESERVER_1': None, + 'NAMESERVER_2': None, + 'NAMESERVER_3': None} + + try: + dns_list = api.sysinv.dns_list(self.request) + + if dns_list: + dns = dns_list[0] + + dns_form_data['uuid'] = dns.uuid + if dns.nameservers: + servers = dns.nameservers.split(",") + for index, server in enumerate(servers): + dns_form_data['NAMESERVER_%s' % (index + 1)] = server + + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve DNS data.")) + + return dns_form_data + + +class UpdatecNTPView(forms.ModalFormView): + form_class = UpdatecNTP + template_name = 'admin/system_config/update_cntp_table.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + context = super(UpdatecNTPView, self).get_context_data(**kwargs) + ntp_list = api.sysinv.ntp_list(self.request) + + if ntp_list: + if "uuid" in ntp_list[0]._attrs: + uuid = ntp_list[0].uuid + + else: + uuid = " " + + else: + uuid = " " + + context['uuid'] = uuid + return context + + def get_initial(self): + ntp_form_data = {'uuid': ' ', + 'NTP_SERVER_1': None, + 'NTP_SERVER_2': None, + 'NTP_SERVER_3': None} + + try: + ntp_list = api.sysinv.ntp_list(self.request) + + if ntp_list: + ntp = ntp_list[0] + + ntp_form_data['uuid'] = ntp.uuid + if ntp.ntpservers: + servers = ntp.ntpservers.split(",") + for index, server in enumerate(servers): + ntp_form_data['NTP_SERVER_%s' % (index + 1)] = server + + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve NTP data.")) + + return ntp_form_data + + +class UpdatecEXT_OAMView(forms.ModalFormView): + form_class = UpdatecEXT_OAM + template_name = 'admin/system_config/update_coam_table.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + context = super(UpdatecEXT_OAMView, self).get_context_data(**kwargs) + extoam_list = api.sysinv.extoam_list(self.request) + + if extoam_list: + if 'uuid' in extoam_list[0]._attrs: + uuid = extoam_list[0].uuid + + else: + uuid = " " + + else: + uuid = " " + + context['uuid'] = uuid + return context + + def get_initial(self): + + oam_form_data = {'uuid': ' ', + 'EXTERNAL_OAM_SUBNET': None, + 'EXTERNAL_OAM_FLOATING_ADDRESS': None, + 'EXTERNAL_OAM_GATEWAY_ADDRESS': None, + 'EXTERNAL_OAM_0_ADDRESS': None, + 'EXTERNAL_OAM_1_ADDRESS': None, + } + + try: + + extoam_list = api.sysinv.extoam_list(self.request) + + if extoam_list: + if extoam_list[0]: + + extoam_attrs = extoam_list[0]._attrs + + if 'uuid' in extoam_attrs: + oam_form_data['uuid'] = extoam_list[0].uuid + + oam_form_data['EXTERNAL_OAM_SUBNET'] = \ + extoam_list[0]._oam_subnet + oam_form_data['EXTERNAL_OAM_FLOATING_ADDRESS'] = \ + extoam_list[0]._oam_floating_ip + oam_form_data['EXTERNAL_OAM_GATEWAY_ADDRESS'] = \ + extoam_list[0]._oam_gateway_ip + oam_form_data['EXTERNAL_OAM_0_ADDRESS'] = \ + extoam_list[0]._oam_c0_ip + oam_form_data['EXTERNAL_OAM_1_ADDRESS'] = \ + extoam_list[0]._oam_c1_ip + + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve OAM data.")) + + return oam_form_data + + +class UpdateiStorageView(forms.ModalFormView): + form_class = UpdateiStorage + success_url = reverse_lazy('horizon:admin:system_config:index') + template_name = 'admin/system_config/update_istorage_table.html' + + def get_context_data(self, **kwargs): + context = super(UpdateiStorageView, self).get_context_data(**kwargs) + + if api.sysinv.is_system_mode_simplex(self.request): + context['is_system_mode_simplex'] = True + + return context + + def get_initial(self): + system = api.sysinv.system_get(self.request) + + fs_list = api.sysinv.controllerfs_list(self.request) + fs_form_data = {fs.name.replace("-", "_"): fs.size for fs in fs_list} + fs_form_data.update({'uuid': system.uuid}) + return fs_form_data + + +class UpdateiStoragePoolsView(forms.ModalFormView): + form_class = UpdateiStoragePools + template_name = 'admin/system_config/update_istorage_pools_table.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + ctxt = super(UpdateiStoragePoolsView, self).get_context_data(**kwargs) + storage_list = api.sysinv.storagepool_list(self.request) + + if storage_list: + if hasattr(storage_list[0], 'uuid'): + uuid = storage_list[0].uuid + + else: + uuid = " " + + else: + uuid = " " + + ctxt['uuid'] = uuid + ctxt['cluster_total'] = storage_list[0].ceph_total_space_gib + + ctxt['configured_quota'] = 0 + # check before adding each value in case it is None + if storage_list[0].cinder_pool_gib: + ctxt['configured_quota'] += storage_list[0].cinder_pool_gib + if storage_list[0].glance_pool_gib: + ctxt['configured_quota'] += storage_list[0].glance_pool_gib + if storage_list[0].ephemeral_pool_gib: + ctxt['configured_quota'] += storage_list[0].ephemeral_pool_gib + if storage_list[0].object_pool_gib: + ctxt['configured_quota'] += storage_list[0].object_pool_gib + + return ctxt + + def get_initial(self): + + storage_form_data = {'uuid': ' ', + 'cinder_pool_gib': None, + 'glance_pool_gib': None, + 'ephemeral_pool_gib': None, + 'object_pool_gib': None} + + try: + + storage_list = api.sysinv.storagepool_list(self.request) + + if storage_list: + if storage_list[0]: + + storage_attrs = storage_list[0]._attrs + + if 'uuid' in storage_attrs: + storage_form_data['uuid'] = storage_list[0].uuid + + if 'cinder_pool_gib' in storage_attrs: + storage_form_data['cinder_pool_gib'] = storage_list[ + 0].cinder_pool_gib + + if 'glance_pool_gib' in storage_attrs: + storage_form_data['glance_pool_gib'] = storage_list[ + 0].glance_pool_gib + + if 'ephemeral_pool_gib' in storage_attrs: + storage_form_data['ephemeral_pool_gib'] = storage_list[ + 0].ephemeral_pool_gib + + if 'object_pool_gib' in storage_attrs: + storage_form_data['object_pool_gib'] = storage_list[ + 0].object_pool_gib + + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve size of Ceph pools.")) + + return storage_form_data + + +###################################################### +# SDN Controller Modal Views # +###################################################### + + +class DetailSDNControllerView(tables.DataTableView): + table_class = SDNControllerTable + template_name = 'admin/system_config/detail_sdn_controller_table.html' + failure_url = reverse_lazy('horizon:admin:system_config:index') + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + uuid = self.kwargs['uuid'] + try: + self._object = api.sysinv.sdn_controller_get(self.request, + uuid) + except Exception: + redirect = self.failure_url + msg = _("Unable to retrieve details for " + "SDN controller") + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_context_data(self, **kwargs): + context = super(DetailSDNControllerView, self).\ + get_context_data(**kwargs) + controller = self._get_object() + context['uuid'] = controller.uuid + context['sdn_controller_table'] = controller + return context + + +class CreateSDNControllerView(forms.ModalFormView): + form_class = CreateSDNController + template_name = 'admin/system_config/create_sdn_controller_table.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + +class UpdateSDNControllerView(forms.ModalFormView): + form_class = UpdateSDNController + template_name = 'admin/system_config/update_sdn_controller_table.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + context = super(UpdateSDNControllerView, self).\ + get_context_data(**kwargs) + controller = self._get_object() + context['uuid'] = controller.uuid + return context + + def _get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + controller_uuid = self.kwargs['uuid'] + try: + self._object = api.sysinv.sdn_controller_get(self.request, + controller_uuid) + except Exception: + redirect = self.success_url + msg = _('Unable to retrieve SDN controller details.') + exceptions.handle(self.request, msg, redirect=redirect) + return self._object + + def get_initial(self): + controller = self._get_object() + data = {'uuid': controller.uuid, + 'ip_address': controller.ip_address, + 'port': controller.port, + 'transport': controller.transport, + 'state': controller.state} + return data + + +###################################################### +# Pipeline/PM Views # +###################################################### +class UpdatePipelineView(forms.ModalFormView): + form_class = EditPipeline + template_name = 'admin/system_config/edit.html' + success_url = reverse_lazy('horizon:admin:system_config:index') + + def get_context_data(self, **kwargs): + context = super(UpdatePipelineView, self).get_context_data(**kwargs) + context['pipeline_name'] = self.kwargs['pipeline_name'] + return context + + def get_initial(self): + pipeline = None + try: + target_pipeline = self.kwargs['pipeline_name'] + pipelines = api.ceilometer.pipeline_list(self.request) + for p in pipelines: + if p.name == target_pipeline: + pipeline = p + break + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve host data.")) + + if pipeline is None: + exceptions.handle(self.request, + _("Unable to retrieve host data.")) + + return {'pipeline_name': pipeline.name, + 'compress': pipeline.compress, + 'max_bytes': pipeline.max_bytes, + 'backup_count': pipeline.backup_count, + 'location': pipeline.location, + 'enabled': pipeline.enabled} diff --git a/cgcs_dashboard/dashboards/dc_admin/__init__.py b/cgcs_dashboard/dashboards/dc_admin/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/dc_admin/cloud_overview/__init__.py b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/cgcs_dashboard/dashboards/dc_admin/cloud_overview/panel.py b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/panel.py new file mode 100755 index 00000000..c4f9e412 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/panel.py @@ -0,0 +1,17 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ + +import horizon + + +class CloudOverview(horizon.Panel): + name = _("Cloud Overview") + slug = 'cloud_overview' diff --git a/cgcs_dashboard/dashboards/dc_admin/cloud_overview/templates/cloud_overview/index.html b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/templates/cloud_overview/index.html new file mode 100755 index 00000000..cb3e5857 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/templates/cloud_overview/index.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Cloud Overview" %}{% endblock %} + +{% block page_header %} + +{% endblock %} + +{% block ng_route_base %} + +{% endblock %} + +{% block main %} + {% csrf_token %} +

    System Controller

    + +
    +

    Subclouds

    + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/dc_admin/cloud_overview/urls.py b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/urls.py new file mode 100755 index 00000000..352d0728 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/urls.py @@ -0,0 +1,17 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import url + +from openstack_dashboard.dashboards.dc_admin.cloud_overview import views + + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), +] diff --git a/cgcs_dashboard/dashboards/dc_admin/cloud_overview/views.py b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/views.py new file mode 100755 index 00000000..1b64f35b --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/cloud_overview/views.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.views import generic + + +class IndexView(generic.TemplateView): + template_name = 'dc_admin/cloud_overview/index.html' diff --git a/cgcs_dashboard/dashboards/dc_admin/dashboard.py b/cgcs_dashboard/dashboards/dc_admin/dashboard.py new file mode 100755 index 00000000..2582487a --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/dashboard.py @@ -0,0 +1,33 @@ +# +# Copyright (c) 2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.utils.translation import ugettext_lazy as _ + +import horizon + + +class DCAdmin(horizon.Dashboard): + name = _("Distributed Cloud Admin") + slug = "dc_admin" + default_panel = 'cloud_overview' + + # Must be admin and in the dcmanager's service region to manage + # distributed cloud + permissions = ('openstack.roles.admin', + 'openstack.services.dcmanager',) + + def allowed(self, context): + # Must be in SystemController region or in RegionOne (and in DC mode) + if context['request'].user.services_region != 'SystemController': + # TODO(tsmith) enhance criteria? + return False + + return super(DCAdmin, self).allowed(context) + +horizon.register(DCAdmin) diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.js new file mode 100755 index 00000000..c1450d5b --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc horizon.dashboard.dc_admin.cloud_overview + * @ngModule + * + * @description + * Provides all of the services and widgets required + * to support and display the cloud overview panel. + */ + angular + .module('horizon.dashboard.dc_admin.cloud_overview', []) + .config(config); + + config.$inject = [ + '$provide', + '$windowProvider' + ]; + + function config($provide, $windowProvider) { + var path = $windowProvider.$get().STATIC_URL + 'dashboard/dc_admin/'; + $provide.constant('horizon.dashboard.dc_admin.basePath', path); + } + +})(); diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.spec.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.spec.js new file mode 100755 index 00000000..a6addd34 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/cloud_overview.module.spec.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +(function() { + 'use strict'; + + describe('horizon.dashboard.dc_admin.cloud_overview', function() { + it('should exist', function() { + expect(angular.module('horizon.dashboard.dc_admin.cloud_overview')).toBeDefined(); + }); + }); + +})(); diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/add_subcloud.service.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/add_subcloud.service.js new file mode 100755 index 00000000..2725315a --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/add_subcloud.service.js @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container-infra.clusters.create.service + * @description Service for the container-infra cluster create modal + */ + angular + .module('horizon.dashboard.dc_admin.cloud_overview') + .factory('horizon.dashboard.dc_admin.cloud_overview.add_subcloud.service', createService); + + createService.$inject = [ + '$location', + 'horizon.app.core.openstack-service-api.dc_manager', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + ]; + + function createService( + $location, dc_manager, actionResult, gettext, $qExtensions, modal, toast + ) { + + var config; + var message = { + success: gettext('Subcloud %s was successfully created.') + }; + + var service = { + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function perform(selected, $scope) { + config = workflow.init('create', gettext('Create'), $scope); + if (typeof selected !== 'undefined') { + config.model.cluster_template_id = selected.id; + } + return modal.open(config).then(submit); + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function submit(context) { + context.model = cleanNullProperties(context.model); + return magnum.createCluster(context.model, false).then(success, true); + } + + function cleanNullProperties(model) { + // Initially clean fields that don't have any value. + // Not only "null", blank too. + for (var key in model) { + if (model.hasOwnProperty(key) && model[key] === null || model[key] === "" || + key === "tabs") { + delete model[key]; + } + } + return model; + } + + function success(response) { + response.data.id = response.data.uuid; + toast.add('success', interpolate(message.success, [response.data.id])); + var result = actionResult.getActionResult() + .created(resourceType, response.data.id); + if (result.result.failed.length === 0 && result.result.created.length > 0) { + $location.path("/project/clusters"); + } else { + return result.result; + } + } + } +})(); \ No newline at end of file diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.controller.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.controller.js new file mode 100755 index 00000000..a1a8d94d --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.controller.js @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + + +(function() { + 'use strict'; + + /** + * @ngdoc dcOverviewCentralTableController + * @ngController + * + * @description + * Controller for the dc_admin cloud overview system controller table. + * Serve as the focal point for table actions. + */ + angular + .module('horizon.dashboard.dc_admin.cloud_overview') + .controller('dcOverviewCentralTableController', dcOverviewCentralTableController); + + dcOverviewCentralTableController.$inject = [ + '$q', + '$scope', + '$timeout', + '$interval', + '$window', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.app.core.openstack-service-api.sysinv', + ]; + + function dcOverviewCentralTableController( + $q, + $scope, + $timeout, + $interval, + $window, + toast, + gettext, + sysinv + ){ + + var ctrl = this; + ctrl.centralClouds = []; + ctrl.icentralClouds = []; + + ctrl.goToCentralAlarmDetails = goToCentralAlarmDetails; + ctrl.goToCentralHostDetails = goToCentralHostDetails; + + // Auto-refresh + ctrl.$interval = $interval; + ctrl.refreshInterval; + ctrl.refreshWaitTime = 5000; + + getData(); + startRefresh(); + + //////////////////////////////// + + function getData() { + // Fetch central cloud data to populate the table + $q.all([ + sysinv.getSystem().success(getSystemSuccess), + sysinv.getAlarmSummary().success(getAlarmSummarySuccess) + ]).then(function(){ + angular.extend(ctrl.centralClouds[0], ctrl.alarmSummary); + }) + } + + function getSystemSuccess(response) { + ctrl.centralClouds = [response]; + } + + function getAlarmSummarySuccess(response) { + ctrl.alarmSummary = response; //Only one summary exists + } + + /////////////////////////// + // REFRESH FUNCTIONALITY // + /////////////////////////// + + function startRefresh() { + if (angular.isDefined(ctrl.refreshInterval)) return; + ctrl.refreshInterval = ctrl.$interval(getData, ctrl.refreshWaitTime); + } + + $scope.$on('$destroy',function(){ + ctrl.stopRefresh(); + }); + + function stopRefresh() { + if (angular.isDefined(ctrl.refreshInterval)) { + ctrl.$interval.cancel(ctrl.refreshInterval); + ctrl.refreshInterval = undefined; + } + } + + ///////////// + // Details // + ///////////// + + function goToCentralAlarmDetails(cloud) { + $window.location.href = "/admin/fault_management/"; + } + + function goToCentralHostDetails(cloud) { + $window.location.href = "/admin/inventory/"; + } + + } +})(); diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.html b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.html new file mode 100755 index 00000000..9de30836 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/central_table.html @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    System NameAlarm StatusLocationActions
    + + {$ cloud.name $} + + + + {$ cloud.status $} + {$ cloud.location | noValue $} + + + + + + + + + +
    +
    +
    +
    +
    +

    Alarm Summary

    +
    +
    N/A
    +
    +
    +
    Critical
    +
    + {$ cloud.critical $} + {$ cloud.critical $} +
    +
    Major
    +
    + {$ cloud.major $} + {$ cloud.major $} +
    +
    Minor
    +
    + {$ cloud.minor $} + {$ cloud.minor $} +
    +
    Warning
    +
    + {$ cloud.warnings $} + {$ cloud.warnings $} +
    +
    +
    + +
    +
    Description
    +
    {$ cloud.description | noValue $}
    +
    Software Version
    +
    {$ cloud.software_version $}
    +
    Created At
    +
    {$ cloud.created_at $}
    +
    Updated At
    +
    {$ cloud.updated_at | noValue $}
    +
    +
    +
    diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.controller.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.controller.js new file mode 100755 index 00000000..4ed99d11 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.controller.js @@ -0,0 +1,411 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + + +(function() { + 'use strict'; + + /** + * @ngdoc dcOverviewCloudTableController + * @ngController + * + * @description + * Controller for the dc_admin overview table. + * Serve as the focal point for table actions. + */ + angular + .module('horizon.dashboard.dc_admin.cloud_overview') + .controller('dcOverviewCloudTableController', dcOverviewCloudTableController); + + dcOverviewCloudTableController.$inject = [ + '$q', + '$scope', + '$timeout', + '$interval', + '$window', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.modal.simple-modal.service', + 'horizon.framework.widgets.modal.deleteModalService', + 'horizon.app.core.openstack-service-api.dc_manager', + 'horizon.app.core.openstack-service-api.keystone', + ]; + + function dcOverviewCloudTableController( + $q, + $scope, + $timeout, + $interval, + $window, + toast, + gettext, + modalFormService, + simpleModalService, + deleteModal, + dc_manager, + keystone + ){ + + var ctrl = this; + ctrl.subClouds = []; + ctrl.isubClouds = []; + ctrl.subCloudSummaries = []; + + //ctrl.globalActions = globalActions; + + ctrl.manage = manage; + ctrl.unmanageSubcloud = unmanageSubcloud; + ctrl.deleteSubcloud = deleteSubcloud; + ctrl.addSubcloud = addSubcloud; + ctrl.editSubcloud = editSubcloud; + ctrl.addSubcloudAction = addSubcloudAction; + ctrl.generateConfig = generateConfig; + ctrl.downloadConfig = downloadConfig; + ctrl.goToAlarmDetails = goToAlarmDetails; + ctrl.goToHostDetails = goToHostDetails; + + // Auto-refresh + ctrl.$interval = $interval; + ctrl.refreshInterval; + ctrl.refreshWaitTime = 5000; + + // Messages + ctrl.endpointErrorMsg = gettext("This subcloud's endpoints are not yet accessible by horizon. Please log out and log back in to access this subcloud."); + + getData(); + startRefresh(); + + //////////////////////////////// + + function getData() { + // Fetch subcloud data to populate the table + $q.all([ + dc_manager.getSubClouds().success(getSubCloudsSuccess), + dc_manager.getSummaries().success(getSummariesSuccess) + ]).then(function(){ + map_subclouds(); + }) + } + + function getSubCloudsSuccess(response) { + ctrl.subClouds = response.items; + } + + function getSummariesSuccess(response) { + ctrl.subCloudSummaries = response.items; + } + + function map_subclouds() { + ctrl.subClouds = $.map(ctrl.subClouds, function (subCloud){ + var match = ctrl.subCloudSummaries.filter(function(summary){return summary.name == subCloud.name;}); + if (match.length == 0){ + // No matching summary to this subcloud + return subCloud; + } + return angular.extend(subCloud, match[0]); + }); + } + + + /////////////////////////// + // REFRESH FUNCTIONALITY // + /////////////////////////// + + function startRefresh() { + if (angular.isDefined(ctrl.refreshInterval)) return; + ctrl.refreshInterval = ctrl.$interval(getData, ctrl.refreshWaitTime); + } + + $scope.$on('$destroy',function(){ + ctrl.stopRefresh(); + }); + + function stopRefresh() { + if (angular.isDefined(ctrl.refreshInterval)) { + ctrl.$interval.cancel(ctrl.refreshInterval); + ctrl.refreshInterval = undefined; + } + } + + + ///////////////// + // UNMANAGE/MANAGE // + ///////////////// + + function manage(cloud) { + var response = dc_manager.editSubcloud(cloud.subcloud_id, {'management-state': 'managed'}); + } + + function unmanageSubcloud(cloud) { + var options = { + title: 'Confirm Subcloud Unmanage', + body: 'Are you sure you want to unmanage subcloud '+cloud.name+'?', + submit: 'Unmanage', + cancel: 'Cancel' + }; + + simpleModalService.modal(options).result.then(function() { + dc_manager.editSubcloud(cloud.subcloud_id, {'management-state': 'unmanaged'}); + }); + } + + + //////////// + // DELETE // + //////////// + + function deleteSubcloud(cloud) { + var scope = { $emit: deleteActionComplete }; + var context = { + labels: { + title: gettext('Confirm Subcloud Delete'), + message: gettext('This will delete subcloud %s, are you sure you want to continue?'), + submit: gettext('Delete'), + success: gettext('Subcloud delete successful.') + }, + deleteEntity: doDelete, + successEvent: 'success', + }; + cloud.id = cloud.subcloud_id; + deleteModal.open(scope, [cloud], context); + + } + function doDelete(id) { + var response = dc_manager.deleteSubcloud(id); + return response; + } + + function deleteActionComplete(eventType) { + return; + } + + + ///////////////// + // CREATE/EDIT // + ///////////////// + + var subcloudSchema = { + type: "object", + properties: { + "name": { + type: "string", + title: "Name"}, + "description": { + type: "string", + title: "Description"}, + "location": { + type: "string", + title: "Location"}, + "management-subnet": { + type: "string", + title: "Management Subnet"}, + "management-start-ip": { + type: "string", + title: "Management Start IP"}, + "management-end-ip": { + type: "string", + title: "Management End IP"}, + "management-gateway-ip": { + type: "string", + title: "Management Gateway IP"}, + "central-gateway-ip": { + type: "string", + title: "Central Ggateway IP"}, + }, + required: ["name", "management-subnet", "management-start-ip", "management-end-ip", "management-gateway-ip", "central-gateway-ip"], + }; + + function addSubcloud() { + var model = { + "name": "", + "description": "", + "location": "", + "management-subnet": "", + "management-start-ip": "", + "management-end-ip": "", + "management-gateway-ip": "", + "central-gateway-ip": ""}; + var config = { + title: gettext('Add Subcloud'), + schema: subcloudSchema, + form: ["*"], + model: model + }; + return modalFormService.open(config).then(function then() { + return ctrl.addSubcloudAction(model); + }); + } + + function addSubcloudAction(model) { + return dc_manager.createSubcloud(model); + } + + var editsubcloudSchema = { + type: "object", + properties: { + "name": { + type: "string", + title: "Name", + readonly: true}, + "description": { + type: "string", + title: "Description"}, + "location": { + type: "string", + title: "Location"}, + } + }; + + function editSubcloud(cloud) { + var model = { + "name": cloud.name, + "description": cloud.description, + "location": cloud.location, + }; + + var config = { + title: gettext('Edit Subcloud'), + schema: editsubcloudSchema, + form: ["*"], + model: model + }; + return modalFormService.open(config).then(function(){ + return dc_manager.editSubcloud(cloud.subcloud_id, model); + }); + } + + + //////////////////// + // GenerateConfig // + //////////////////// + + var generateConfigSchema = { + type: "object", + properties: { + "management-interface-port": { + type: "string", + title: "Management Interface Port",}, + "management-interface-mtu": { + type: "number", + title: "Management Interface MTU"}, + "oam-subnet": { + type: "string", + title: "OAM Subnet"}, + "oam-gateway-ip": { + type: "string", + title: "OAM Gateway IP"}, + "oam-floating-ip": { + type: "string", + title: "OAM Floating IP"}, + "oam-unit-0-ip": { + type: "string", + title: "OAM Unit-0 IP"}, + "oam-unit-1-ip": { + type: "string", + title: "OAM Unit-1 IP"}, + "oam-interface-port": { + type: "string", + title: "OAM Interface Port"}, + "oam-interface-mtu": { + type: "number", + title: "OAM Interface MTU"}, + }, + }; + + function generateConfig(cloud) { + var model = {}; + var config = { + title: gettext('Generate Subcloud Configuration File'), + schema: generateConfigSchema, + form: ["*"], + model: model, + }; + return modalFormService.open(config).then(function(){ + return dc_manager.generateConfig(cloud.subcloud_id, model).success(function(response) { + downloadConfig(response.config, cloud.name + "_config.ini"); + }); + }); + } + + +// function generateConfig(cloud) { +// return dc_manager.generateConfig(cloud.subcloud_id, {}).success(function(response) { +// downloadConfig(response.config, cloud.name + "_config.ini"); +// }); +// } + + function downloadConfig(text, filename) { + // create text file as object url + var blob = new Blob([ text ], { "type" : "text/plain" }); + window.URL = window.URL || window.webkitURL; + var fileurl = window.URL.createObjectURL(blob); + // provide text as downloaded file + $timeout(function() { + //Update view + var a = angular.element(''); + a.attr("href", fileurl); + a.attr("download", filename); + a.attr("target", "_blank"); + angular.element(document.body).append(a); + a[0].click(); + a.remove(); + }, 0); + } + + + ///////////// + // Details // + ///////////// + + function goToAlarmDetails(cloud) { + // TODO handle tabs? + + // Check to see that the subcloud is managed + if (cloud.management_state != 'managed') { + toast.add('error', + gettext('The subcloud must be in the managed state before you can access detailed views.')); + return; + } + + keystone.getCurrentUserSession().success(function(session){ + session.available_services_regions.indexOf(cloud.name) + if (session.available_services_regions.indexOf(cloud.name) > -1) { + $window.location.href = "/auth/switch_services_region/"+ cloud.name + "/?next=/admin/fault_management/"; + } else { + toast.add('error', ctrl.endpointErrorMsg); + // TODO(tsmith) should we force a logout here with an reason message? + } + }).error(function(error) { + toast.add('error', + gettext("Could not retrieve current user's session.")); + }); + } + + function goToHostDetails(cloud) { + // Check to see that the subcloud is managed + if (cloud.management_state != 'managed') { + toast.add('error', + gettext('The subcloud must be in the managed management state before you can access detailed views.')); + return; + } + + keystone.getCurrentUserSession().success(function(session){ + if (session.available_services_regions.indexOf(cloud.name) > -1) { + $window.location.href = "/auth/switch_services_region/"+ cloud.name + "/?next=/admin/inventory/"; + } else { + toast.add('error', ctrl.endpointErrorMsg); + } + }).error(function(error) { + toast.add('error', + gettext("Could not retrieve current user's session.")); + }); + } + + } +})(); diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.html b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.html new file mode 100755 index 00000000..1870c634 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/cloud_overview/table/subcloud_table.html @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + + + +
    + +
    +
    Cloud NameManagement StateAvailability StatusAlarm StatusSync StatusLocationActions
    + + {$ cloud.name $}{$ cloud.management_state $} + + + {$ cloud.availability_status $} + + + + {$ cloud.status $} + + + + + {$ cloud.sync_status $}{$ cloud.location | noValue $} + + + + + + + + + +
    + +
    +
    +
    Description
    +
    {$ cloud.description | noValue $}
    +
    +
    +
    +
    +
    +

    Management Network

    +
    Subnet
    +
    {$ cloud.management_subnet $}
    +
    Start IP
    +
    {$ cloud.management_start_ip $}
    +
    End IP
    +
    {$ cloud.management_end_ip $}
    +
    Gateway IP
    +
    {$ cloud.management_gateway_ip $}
    +
    +
    +
    SystemController Gateway IP
    +
    {$ cloud.systemcontroller_gateway_ip $}
    +
    + +
    +

    Sync Status

    +
    {$ status.endpoint_type $}
    +
    + + + + {$ status.sync_status $} +
    + +

    Alarm Summary

    +
    +
    N/A
    +
    +
    +
    Critical
    +
    + {$ cloud.critical $} + {$ cloud.critical $} +
    +
    Major
    +
    + {$ cloud.major $} + {$ cloud.major $} +
    +
    Minor
    +
    + {$ cloud.minor $} + {$ cloud.minor $} +
    +
    Warning
    +
    + {$ cloud.warnings $} + {$ cloud.warnings $} +
    +
    +
    + +

    +
    Software Version
    +
    {$ cloud.software_version $}
    +
    Created At
    +
    {$ cloud.created_at $}
    +
    Updated At
    +
    {$ cloud.updated_at | noValue $}
    +
    +
    +
    diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.js new file mode 100755 index 00000000..4c080c83 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc horizon.dashboard.dc_admin + * @ngModule + * + * @description + * Dashboard module to host distributed cloud admin panels. + */ + angular + .module('horizon.dashboard.dc_admin', [ + 'horizon.dashboard.dc_admin.cloud_overview', + 'ngRoute' + ]); +})(); diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.spec.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.spec.js new file mode 100755 index 00000000..8c3d784a --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_admin.module.spec.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +(function() { + 'use strict'; + + describe('horizon.dashboard.dc_admin', function() { + it('should exist', function() { + expect(angular.module('horizon.dashboard.dc_admin')).toBeDefined(); + }); + }); + + describe('horizon.dashboard.dc_admin.basePath constant', function() { + var dc_adminBasePath, staticUrl; + + beforeEach(module('horizon.dashboard.dc_admin')); + beforeEach(inject(function($injector) { + dc_adminBasePath = $injector.get('horizon.dashboard.dc_admin.basePath'); + staticUrl = $injector.get('$window').STATIC_URL; + })); + + it('should equal to "/static/dashboard/dc_admin/"', function() { + expect(dc_adminBasePath).toEqual(staticUrl + 'dashboard/dc_admin/'); + }); + }); + +})(); diff --git a/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_manager.service.js b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_manager.service.js new file mode 100755 index 00000000..d6b08347 --- /dev/null +++ b/cgcs_dashboard/dashboards/dc_admin/static/dashboard/dc_admin/dc_manager.service.js @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2017 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +(function () { + 'use strict'; + + angular + .module('horizon.app.core.openstack-service-api') + .factory('horizon.app.core.openstack-service-api.dc_manager', DCManagerAPI); + + DCManagerAPI.$inject = [ + '$q', + 'horizon.framework.util.http.service', + 'horizon.framework.widgets.toast.service', + '$http' + ]; + + function DCManagerAPI($q, apiService, toastService, $http) { + var service = { + getSummaries: getSummaries, + createSubcloud: createSubcloud, + editSubcloud: editSubcloud, + getSubClouds: getSubClouds, + deleteSubcloud: deleteSubcloud, + generateConfig: generateConfig + }; + + var csrf_token = $('input[name=csrfmiddlewaretoken]').val(); + $http.defaults.headers.post['X-CSRFToken'] = csrf_token; + $http.defaults.headers.common['X-CSRFToken'] = csrf_token; + $http.defaults.headers.put['X-CSRFToken'] = csrf_token; + + return service; + + + /////////////// + // Summaries // + /////////////// + + function getSummaries() { + return apiService.get('/api/dc_manager/alarm_summaries/') + .error(function () { + toastService.clearErrors(); + toastService.add('error', gettext('Unable to retrieve the subcloud alarm summaries.')); + }); + } + + + /////////////// + // SubClouds // + /////////////// + + /** + * @name createSubcloud + * @description + * Create a subcloud + * @param {string} model a dict of attributes for the new subcloud. + * @returns {Object} The result of the API call + */ + function createSubcloud(model) { + return apiService.put( + '/api/dc_manager/subclouds/', + { + data: model, + } + ) + .error(function (error, status) { + var msg; + if (error.indexOf("{% trans "Attach To Instance" %} +
    + {% include "horizon/common/_form_fields.html" %} +
    + {% endif %} +{% endblock %} + +{% block modal-footer %} + Cancel + {% if show_attach %} + + {% endif %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_create.html b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_create.html new file mode 100755 index 00000000..8d0f1bbd --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_create.html @@ -0,0 +1,27 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}{% endblock %} +{% block form_action %}{% url 'horizon:project:server_groups:create' %}?{{ request.GET.urlencode }}{% endblock %} + +{% block modal_id %}create_server_group_modal{% endblock %} +{% block modal-header %}{% trans "Create Server Group" %}{% endblock %} + +{% block modal-body %} +
    +
    + {% include "horizon/common/_form_fields.html" %} +
    +
    + +
    +

    {% trans "Description" %}:

    +

    {% trans "From here you can create a new server group" %}

    +
    +{% endblock %} + +{% block modal-footer %} + {% trans "Cancel" %} + +{% endblock %} diff --git a/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_detail_overview.html b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_detail_overview.html new file mode 100755 index 00000000..e366852a --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/_detail_overview.html @@ -0,0 +1,53 @@ +{% load i18n sizeformat parse_date %} +{% load url from future %} + +

    {% trans "Server Group Overview" %}: {{server_group.name }}

    + +
    +

    {% trans "Info" %}

    +
    +
    +
    {% trans "Name" %}
    +
    {{ server_group.name }}
    +
    {% trans "ID" %}
    +
    {{ server_group.id }}
    +
    {% trans "Status" %}
    +
    {{ server_group.status|capfirst }}
    +
    +
    + +
    +

    {% trans "Members" %}

    +
    +
    + {% for member in server_group.members_display %} +
    + {% url 'horizon:project:instances:detail' member.id as instance_url%} + {{ member.instance.name }} ({{ member.id }}) +
    + {% empty %} +
    {% trans "No members" %}
    + {% endfor %} +
    +
    + +
    +

    {% trans "Policies" %}

    +
    +
    + {% for policy in server_group.policies %} +
    {{ policy }}
    + {% endfor %} +
    +
    + +
    +

    {% trans "Metadata" %}

    +
    +
    + {% for key, value in server_group.metadata.items %} +
    {{ key }}
    +
    {{ value }}
    + {% endfor %} +
    +
    diff --git a/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/attach.html b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/attach.html new file mode 100755 index 00000000..4b4dad86 --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/attach.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Manage Volume Attachments" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Manage Volume Attachments") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/volumes/_attach.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/create.html b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/create.html new file mode 100755 index 00000000..10bbff14 --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Server Group" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create an Server Group") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/server_groups/_create.html' %} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/detail.html b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/detail.html new file mode 100755 index 00000000..3ce02ba3 --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n breadcrumb_nav %} +{% block title %}{% trans "Server Group Details" %}{% endblock %} + +{% block main %} +
    +
    + {{ tab_group.render }} +
    +
    +{% endblock %} diff --git a/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/index.html b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/index.html new file mode 100755 index 00000000..44a03e4c --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/templates/server_groups/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Server Groups" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Server Groups") %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/cgcs_dashboard/dashboards/project/server_groups/urls.py b/cgcs_dashboard/dashboards/project/server_groups/urls.py new file mode 100755 index 00000000..04a8a769 --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/urls.py @@ -0,0 +1,39 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +from django.conf.urls import url + +from openstack_dashboard.dashboards.project.server_groups import views + + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^create/$', views.CreateView.as_view(), name='create'), + url(r'^(?P[^/]+)/attach/$', + views.EditAttachmentsView.as_view(), + name='attach'), + url(r'^(?P[^/]+)/$', + views.DetailView.as_view(), + name='detail'), +] diff --git a/cgcs_dashboard/dashboards/project/server_groups/views.py b/cgcs_dashboard/dashboards/project/server_groups/views.py new file mode 100755 index 00000000..63397b92 --- /dev/null +++ b/cgcs_dashboard/dashboards/project/server_groups/views.py @@ -0,0 +1,154 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Copyright (c) 2013-2017 Wind River Systems, Inc. +# +# The right to copy, distribute, modify, or otherwise make use +# of this software may be licensed only pursuant to the terms +# of an applicable Wind River license agreement. +# + + +""" +Views for managing server groups. +""" + +from django.core.urlresolvers import reverse_lazy # noqa +from django.utils.translation import ugettext_lazy as _ # noqa + +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon import tabs + +from openstack_dashboard import api +from openstack_dashboard.usage import quotas + +from openstack_dashboard.dashboards.project.server_groups \ + import forms as project_forms + +from openstack_dashboard.dashboards.project.server_groups \ + import tables as project_tables +from openstack_dashboard.dashboards.project.server_groups \ + import tabs as project_tabs + + +# server groups don't currently support pagination +class IndexView(tables.DataTableView): + table_class = project_tables.ServerGroupsTable + template_name = 'project/server_groups/index.html' + page_title = _("Server Groups") + + def get_data(self): + try: + server_groups = api.nova.server_group_list( + self.request) + except Exception: + server_groups = [] + exceptions.handle(self.request, + _('Unable to retrieve server groups.')) + return server_groups + + +class DetailView(tabs.TabView): + tab_group_class = project_tabs.ServerGroupDetailTabs + template_name = 'project/server_groups/detail.html' + page_title = 'Server Group Details' + + +class CreateView(forms.ModalFormView): + form_class = project_forms.CreateForm + template_name = 'project/server_groups/create.html' + success_url = reverse_lazy("horizon:project:server_groups:index") + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + try: + context['usages'] = quotas.tenant_limit_usages(self.request) + except Exception: + exceptions.handle(self.request) + return context + + +class EditAttachmentsView(tables.DataTableView, forms.ModalFormView): + table_class = project_tables.AttachmentsTable + form_class = project_forms.AttachForm + template_name = 'project/server_groups/attach.html' + success_url = reverse_lazy("horizon:project:server_groups:index") + + def get_object(self): + if not hasattr(self, "_object"): + volume_id = self.kwargs['volume_id'] + try: + self._object = api.cinder.volume_get(self.request, volume_id) + except Exception: + self._object = None + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return self._object + + def get_data(self): + try: + volumes = self.get_object() + attachments = [att for att in volumes.attachments if att] + except Exception: + attachments = [] + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return attachments + + def get_initial(self): + try: + instances, has_more = api.nova.server_list(self.request) + except Exception: + instances = [] + exceptions.handle(self.request, + _("Unable to retrieve attachment information.")) + return {'volume': self.get_object(), + 'instances': instances} + + def get_form(self): + if not hasattr(self, "_form"): + form_class = self.get_form_class() + self._form = super(EditAttachmentsView, self).get_form(form_class) + return self._form + + def get_context_data(self, **kwargs): + context = super(EditAttachmentsView, self).get_context_data(**kwargs) + context['form'] = self.get_form() + volume = self.get_object() + if volume and volume.status == 'available': + context['show_attach'] = True + else: + context['show_attach'] = False + context['volume'] = volume + if self.request.is_ajax(): + context['hide'] = True + return context + + def get(self, request, *args, **kwargs): + # Table action handling + handled = self.construct_tables() + if handled: + return handled + return self.render_to_response(self.get_context_data(**kwargs)) + + def post(self, request, *args, **kwargs): + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.get(request, *args, **kwargs) diff --git a/cgcs_dashboard/enabled/_1031_WRS_project_server_groups_panel.py b/cgcs_dashboard/enabled/_1031_WRS_project_server_groups_panel.py new file mode 100755 index 00000000..44de6166 --- /dev/null +++ b/cgcs_dashboard/enabled/_1031_WRS_project_server_groups_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'server_groups' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'compute' + +# Python panel class of the PANEL to be added. +ADD_PANEL = \ + 'cgcs_dashboard.dashboards.project.server_groups.panel.ServerGroups' diff --git a/cgcs_dashboard/enabled/_2005_admin_platform_panel_group.py b/cgcs_dashboard/enabled/_2005_admin_platform_panel_group.py new file mode 100755 index 00000000..4ff9a7d0 --- /dev/null +++ b/cgcs_dashboard/enabled/_2005_admin_platform_panel_group.py @@ -0,0 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'platform' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _('Platform') +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'admin' diff --git a/cgcs_dashboard/enabled/_2032_WRS_admin_fault_management_panel.py b/cgcs_dashboard/enabled/_2032_WRS_admin_fault_management_panel.py new file mode 100755 index 00000000..1fa3b726 --- /dev/null +++ b/cgcs_dashboard/enabled/_2032_WRS_admin_fault_management_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'fault_management' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'cgcs_dashboard.dashboards.admin.' \ + 'fault_management.panel.FaultManagement' diff --git a/cgcs_dashboard/enabled/_2034_WRS_platform_software_management_panel.py b/cgcs_dashboard/enabled/_2034_WRS_platform_software_management_panel.py new file mode 100755 index 00000000..25f1edae --- /dev/null +++ b/cgcs_dashboard/enabled/_2034_WRS_platform_software_management_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'software_management' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'cgcs_dashboard.dashboards.admin.software_management.' \ + 'panel.SoftwareManagement' diff --git a/cgcs_dashboard/enabled/_2035_WRS_admin_inventory_panel.py b/cgcs_dashboard/enabled/_2035_WRS_admin_inventory_panel.py new file mode 100755 index 00000000..603ef162 --- /dev/null +++ b/cgcs_dashboard/enabled/_2035_WRS_admin_inventory_panel.py @@ -0,0 +1,9 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'inventory' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'cgcs.dashboards.admin.inventory.panel.Inventory' diff --git a/cgcs_dashboard/enabled/_2036_WRS_admin_providernets.py b/cgcs_dashboard/enabled/_2036_WRS_admin_providernets.py new file mode 100755 index 00000000..ef857269 --- /dev/null +++ b/cgcs_dashboard/enabled/_2036_WRS_admin_providernets.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'providernets' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = ('cgcs_dashboard.dashboards.admin.' + 'providernets.panel.Providernets') diff --git a/cgcs_dashboard/enabled/_2037_WRS_admin_host_topology_panel.py b/cgcs_dashboard/enabled/_2037_WRS_admin_host_topology_panel.py new file mode 100755 index 00000000..c255401a --- /dev/null +++ b/cgcs_dashboard/enabled/_2037_WRS_admin_host_topology_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'host_topology' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'cgcs_dashboard.dashboards.admin.host_topology.panel.' \ + 'HostTopology' diff --git a/cgcs_dashboard/enabled/_2038_WRS_platform_storage_overview_panel.py b/cgcs_dashboard/enabled/_2038_WRS_platform_storage_overview_panel.py new file mode 100755 index 00000000..43137233 --- /dev/null +++ b/cgcs_dashboard/enabled/_2038_WRS_platform_storage_overview_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'storage_overview' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'cgcs_dashboard.dashboards.admin.storage_overview.' \ + 'panel.StorageOverview' diff --git a/cgcs_dashboard/enabled/_2039_WRS_admin_system_config.py b/cgcs_dashboard/enabled/_2039_WRS_admin_system_config.py new file mode 100755 index 00000000..6b07ee62 --- /dev/null +++ b/cgcs_dashboard/enabled/_2039_WRS_admin_system_config.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'system_config' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'platform' + +# Python panel class of the PANEL to be added. +ADD_PANEL = ('cgcs_dashboard.dashboards.admin.' + 'system_config.panel.SystemConfig') diff --git a/cgcs_dashboard/enabled/_2061_WRS_admin_server_groups_panel.py b/cgcs_dashboard/enabled/_2061_WRS_admin_server_groups_panel.py new file mode 100755 index 00000000..76d00bd7 --- /dev/null +++ b/cgcs_dashboard/enabled/_2061_WRS_admin_server_groups_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'server_groups' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'compute' + +# Python panel class of the PANEL to be added. +ADD_PANEL = \ + 'cgcs_dashboard.dashboards.admin.server_groups.panel.ServerGroups' diff --git a/cgcs_dashboard/enabled/_2200_WRS_dc_admin.py b/cgcs_dashboard/enabled/_2200_WRS_dc_admin.py new file mode 100755 index 00000000..d237c160 --- /dev/null +++ b/cgcs_dashboard/enabled/_2200_WRS_dc_admin.py @@ -0,0 +1,14 @@ +# The slug of the dashboard to be added to HORIZON['dashboards']. Required. +DASHBOARD = 'dc_admin' +# If set to True, this dashboard will be set as the default dashboard. +DEFAULT = False +# A dictionary of exception classes to be added to HORIZON['exceptions']. +ADD_EXCEPTIONS = {} +# A list of applications to be added to INSTALLED_APPS. +ADD_INSTALLED_APPS = ['cgcs_dashboard', ] + +ADD_ANGULAR_MODULES = [ + 'horizon.dashboard.dc_admin', +] + +AUTO_DISCOVER_STATIC_FILES = True diff --git a/cgcs_dashboard/enabled/_2210_WRS_dc_admin_cloud_overview_panel.py b/cgcs_dashboard/enabled/_2210_WRS_dc_admin_cloud_overview_panel.py new file mode 100755 index 00000000..44e40ab5 --- /dev/null +++ b/cgcs_dashboard/enabled/_2210_WRS_dc_admin_cloud_overview_panel.py @@ -0,0 +1,17 @@ +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'dc_admin' + +# The slug of the panel group the PANEL is associated with. +# If you want the panel to show up without a panel group, +# use the panel group "default". +PANEL_GROUP = 'default' + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'cloud_overview' + +# If set to True, this settings file will not be added to the settings. +DISABLED = False + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'cgcs_dashboard.dashboards.' \ + 'dc_admin.cloud_overview.panel.CloudOverview' diff --git a/cgcs_dashboard/enabled/__init__.py b/cgcs_dashboard/enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cgcs_dashboard/exceptions.py b/cgcs_dashboard/exceptions.py new file mode 100755 index 00000000..8738a775 --- /dev/null +++ b/cgcs_dashboard/exceptions.py @@ -0,0 +1,31 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cgtsclient import exc as cgtsclient + + +UNAUTHORIZED = ( + cgtsclient.HTTPUnauthorized, +) + + +NOT_FOUND = ( + cgtsclient.HTTPNotFound, +) + + +RECOVERABLE = ( + cgtsclient.HTTPBadRequest, + cgtsclient.HTTPConflict, + cgtsclient.CommunicationError, +) diff --git a/cgcs_dashboard/static/js/horizon.hosttopology.js b/cgcs_dashboard/static/js/horizon.hosttopology.js new file mode 100755 index 00000000..d796058a --- /dev/null +++ b/cgcs_dashboard/static/js/horizon.hosttopology.js @@ -0,0 +1,965 @@ +/* Namespace for core functionality related to Host Topology. */ + +horizon.host_topology = { + model: null, + svg:'#topology_canvas', + svg_container:'#topologyCanvasContainer', + detail_container:'#detail_view', + zoom_container:'#zoom_container', + alarm_tmpl: '#topology_template > .list_alarm', + network_tmpl: '#topology_template > .network_container_normal', + host_tmpl: '#topology_template > .host_body', + segment_alarm_id: "300.005", + locked_str: 'locked', + zoom : null, + network_index: {}, + reload_duration: 10000, + labels: true, + detail_url: null, + selected_entity: null, + network_height : 0, + element_properties:{ + network_width:250, + network_min_height:500, + network_padding_top:20, + top_margin:80, + default_height:80, + margin:48, + host_x:98.5, + port_text_margin:{x:8,y:-4}, + lldp_text_margin:{x:3,y:12}, + host_width:70, + conn_margin:16, //28 + conn_height:6, + conn_width:82, + lldp_text_height:12, + texts_bg_y:62, + type_y:80, + hostname_offset:17, + cidr_margin:5, + lldp_name_max_size_small:13, + lldp_name_max_size:40, + host_name_max_size:20, + name_suffix:'..' + }, + init:function() { + var self = this; + self.$container = $(self.svg_container); + self.$loading_template = horizon.networktopologyloader.setup_loader($(self.$container)); + if($('#hosttopology').length === 0) { + return; + } + self.color = d3.scale.category10(); + + // Drag behaviour + var svg = d3.select(self.svg); + self.zoom = d3.behavior.zoom().on("zoom", self.zoomed); + svg.call(self.zoom); + + // Unbind 'zoom' behaviour + svg.on("dblclick.zoom", null); + svg.on("wheel.zoom", null); + svg.on("mousewheel.zoom", null); + + // Initialize label toggle button + var labels = horizon.cookies.get('host_topo_labels'); + if (labels && (labels === "visible" || labels === "hidden")) { + self.labels = labels; + } else { + self.labels = "visible"; + horizon.cookies.put('host_topo_labels',self.labels); + } + $('#toggleLabels > .btn').each(function(){ + var $this = $(this); + if(self.labels === "hidden") { + $this.addClass('active'); + } else { + $this.removeClass('active'); + } + }); + if (self.labels === "hidden") { + $('#toggleLabels > .btn').trigger("click"); + } + + $('#toggleLabels > .btn').click(function(){ + if ($('#toggleLabels > .btn input').is(':checked')) { + self.labels = "hidden" + } else { + self.labels = "visible" + } + $('.port_text, .lldp_text').attr("visibility", self.labels); + horizon.cookies.put('host_topo_labels',self.labels); + + $('#toggleLabels > .btn').each(function(){ + var $this = $(this); + if(self.labels === "hidden") { + $this.addClass('active'); + } else { + $this.removeClass('active'); + } + }); + }); + + // Initialize host list sorting behaviour + $('#host_list_search').keyup(function(){ + var text = $(this).val().toLowerCase(); + $('#host_list > a').each(function(){ + var entry_text = $(this).text(), + show_entry = entry_text.toLowerCase().indexOf(text) !== -1; + $(this).toggle(show_entry); + }); + }); + // Initialize providernet list sorting behaviour + $('#network_list_search').keyup(function(){ + var text = $(this).val().toLowerCase(); + $('#network_list > a').each(function(){ + var entry_text = $(this).text(), + show_entry = entry_text.toLowerCase().indexOf(text) !== -1; + $(this).toggle(show_entry); + }); + }); + + this.$detail_view = $('#detail_view'); + this.$network_list = $('#network_list'); + this.$host_list = $('#host_list'); + + self.load_host_info(); + }, + load_host_info:function(){ + var self = this; + if($('#hosttopology').length === 0) { + return; + } + $.getJSON($('#hosttopology').data('hosttopology') + '?' + $.now(), + function(data) { + self.model = data; + self.data_convert(); + setTimeout(function(){ + self.load_host_info(); + }, self.reload_duration); + } + ); + }, + data_convert:function() { + var self = this; + var model = self.model; + $.each(model.networks, function(index, network) { + self.network_index[network.id] = index; + }); + var element_properties = self.element_properties; + self.network_height = element_properties.top_margin; + + // Skip straight to drawing if there are no networks + if (model.networks.length <= 0) { + self.draw_topology(); + self.$loading_template.hide(); + return; + } + + // Remove hosts without compute functionality + model.hosts = $.grep(model.hosts, function (host, i){ + if (host.subfunctions && host.subfunctions.indexOf('compute') !== -1){ + return true; + } + return false; + }) + + $.each(model.hosts, function(index, host) { + var hasifs = (host.interfaces.length <= 0) ? false : true; + + // Attach alarms to this host + host.alarm_level = 0; //0=No alarm, 4=critical + host.alarms = []; + $.each(model.alarms, function(index, alarm) { + ids = alarm.entity_instance_id.split("."); + for (i=0; i element_properties.default_height) ? height : + element_properties.default_height; + host.pos_y = self.network_height; + host.port_height = element_properties.conn_height; + host.port_margin = element_properties.conn_margin; + self.network_height += host.height + element_properties.margin; + + // Add host to its table + found = false; + self.$host_list.find('a').each(function(index) { + if ($(this).text().trim() == host.hostname){ + found = true; + return false; + } + }); + if (!found) { + self.$host_list.append( + $('') + .attr('href',"#") + .attr('class',"list-group-item list-group-item-action") + .attr("id","host-" + host.hostname) + .append(host.hostname) + .on('click',function(d){ + self.zoom_to(d,host); + self.select_host(host); + }) + ); + // Append the alarm icon to the host entry + self.$host_list.find('a#host-'+host.hostname).prepend(d3.select(self.alarm_tmpl).node().cloneNode(true)); + } + // Set the list's alarm level for this host + list_alarm = d3.select('#host_list a#host-'+host.hostname+' .alarm'); + list_alarm.classed("level_1 level_2 level_3 level_4", false); + list_alarm.classed('level_'+host.alarm_level, true); + + }); + + $.each(model.networks, function(index, network) { + network.hosts = []; + network.connected_hosts = []; + + // Attach alarms to the network + network.alarms = []; + network.alarm_level = 0; + $.each(model.alarms, function(index, alarm) { + ids = alarm.entity_instance_id.split("."); + for (i=0; i connection.alarm_level) + connection.alarm_level = network.alarm_level; + return false; + } + }); + }); + + // Add network to its table + found = false; + self.$network_list.find('a').each(function(index) { + if ($(this).text().trim() == network.name){ + found = true; + return false; + } + }); + if (!found) { + self.$network_list.append( + $('') + .attr('href',"#") + .attr('class',"list-group-item list-group-item-action") + .attr("id","net-" + network.name) + .append(network.name) + .on('click',function(d){ + self.zoom_to(d,network); + self.select_network(network); + }) + ); + // Append the alarm icon to the network entry + self.$network_list.find('a#net-'+network.name).prepend(d3.select(self.alarm_tmpl).node().cloneNode(true)); + } + // Set the list's alarm level for this host + list_alarm = d3.select('#network_list a#net-'+network.name+' .alarm'); + list_alarm.classed("level_1 level_2 level_3 level_4", false); + list_alarm.classed('level_'+network.alarm_level, true); + + }); + self.network_height += element_properties.top_margin; + self.network_height = (self.network_height > element_properties.network_min_height) ? + self.network_height : element_properties.network_min_height; + + //console.log(model); + self.draw_topology(); + self.$loading_template.hide(); + }, + load_detail:function(spin){ + scroll = typeof b !== 'undefined' ? b : false; + var self = this; + + // Load host into detail view + if (self.detail_url === null) + return; + if (spin) { + $(self.detail_container).html(""); + self.$loading_detail_template = horizon.networktopologyloader.setup_loader($(self.detail_container)); + } + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function() { + if (this.readyState == this.DONE) { + if(this.status == 200) { + if (this.responseText.indexOf('main_content') !== -1 || + this.responseText.indexOf('login-title') !== -1) { + // We've been redirected to a fallback url (full page or login) + self.$loading_detail_template.hide(); + $(self.detail_container).html("Error retrieving details from url " + self.detail_url + " Status: " + this.status); + } else { + self.detail_callback(self, this.responseText); + self.$loading_detail_template.hide(); + } + return; + }; + self.$loading_detail_template.hide(); + $(self.detail_container).html("Error retrieving details from url " + self.detail_url + " Status: " + this.status); + } + } + tab = $('#detail_view .nav-tabs li.active a').attr('href'); + xmlHttp.open("GET", (tab != undefined) ? self.detail_url+tab : self.detail_url, true); + xmlHttp.send(null); + }, + detail_callback:function(self, response) { + tab_index = this.$detail_view.find('.nav-tabs li.active').index(); + $(self.detail_container).html(response); + if (tab_index !== -1) { + this.$detail_view.find('.nav-tabs li:nth-child('+(tab_index+1)+') a').trigger('click'); + } + + // Special functionality on loaded host detail view + this.$detail_view.find('table#interfaces tr:not(:first):not(:last)').each(function(i,d) { + $.each(self.selected_entity.alarms, function(index, alarm) { + $(d).removeClass('status_down') + if (self.str_in_alarm($(d).attr('data-display'), alarm) || + self.str_in_alarm($(d).attr('data-object-id'), alarm)) { + $(d).addClass('status_down') + } + }); + }); + // Special functionality on loaded providernet detail view + this.$detail_view.find('table#provider_network_ranges tr:not(:first):not(:last)').each(function(i,d) { + $.each(self.selected_entity.alarms, function(index, alarm) { + $(d).removeClass('status_down') + if (self.segment_in_alarm(parseInt($(d).children("td:nth-child(4)").text()), parseInt($(d).children("td:nth-child(5)").text()), alarm)) { + $(d).addClass('status_down') + } + }); + }); + + }, + segment_in_alarm:function(min, max, alarm) { + var self = this; + if (alarm.alarm_id !== self.segment_alarm_id) + return false; // Unrelated alarm + if (alarm.reason_text.indexOf("ranges") == -1) + return false; // Generic providernetwork alarm, for flat networks + + // Retrieve the csv (with spaces) of failed segment ranges + desc = alarm.reason_text.substring(alarm.reason_text.indexOf("ranges")+7, alarm.reason_text.indexOf(" on host")); + desc = desc.split(", "); + + for (i=0; i 50) + new_x = 50; + else if (new_x < (-parseInt($(self.zoom_container)[0].getBoundingClientRect().width) + $(self.svg).width())) + new_x = -parseInt($(self.zoom_container)[0].getBoundingClientRect().width) + $(self.svg).width() - 100; + + translate = [new_x , new_y]; + + // Only zoom if entity is not already visible + if (entity.hostname) { + // Note: visible_ is the direct offset from the viewport to the entity + visible_x = net_x + target[0] + current[0]; + visible_y = target[1] + current[1]; + if (!(visible_x < 0 || + visible_x > $(self.svg).width() - self.element_properties.host_width || + visible_y < 0 || + visible_y > $(self.svg).height() - self.element_properties.host_width)) + return; + } else { + if (!(target[0] < -current[0] || + target[0] > -current[0] + $(self.svg).width() - 17)) + return; + } + + d3.select(self.zoom_container).transition().duration(700) + .attr('transform', "translate(" + translate[0] + "," + translate[1] + ")scale(1)"); + + // Update the zoom to the new location + self.zoom.scale(1); + self.zoom.translate(translate); + + // Fix pnet names to top of view + if (new_y > 25){ + new_y = 25; + } + + d3.selectAll('svg#topology_canvas .network-name').transition().duration(700) + .attr('y',-new_y + 20); + + horizon.cookies.put('host_topo_position', translate); + }, + zoomed:function(given_translate) { + if (typeof given_translate !== 'undefined') + translate = given_translate; + else + translate = d3.event.translate; + + this.$svg_canvas = $('svg#topology_canvas'); + var zoom_container = d3.select('#zoom_container'); + zoom_container.attr("transform", "translate(" + translate + ")"); + + // Fix pnet names to top of view + if (-translate[1] > parseInt(this.$svg_canvas.find('.network-rect')[0].getAttribute("height"))) { + // Lock to bottom of network bars + this.$svg_canvas.find('.network-name').attr('y', parseInt(this.$svg_canvas.find('.network-rect')[0].getAttribute("height")) + 22); + } else if (translate[1] < 25){ + // Scrolling with bars + this.$svg_canvas.find('.network-name').attr('y',-translate[1] + 20); + } else { + // Lock to top of network bars + this.$svg_canvas.find('.network-name').attr('y', -5); + } + + horizon.cookies.put('host_topo_position', translate); + }, + add_glow:function(entity) { + if (entity.hostname){ + highlight = d3.select('#topology_canvas #id_host_'+entity.hostname+' .frame'); + } else { + highlight = d3.select('#topology_canvas #id_net_'+entity.name+' .network-rect'); + } + // Deselect everything and add glow to newly selected element + d3.selectAll('.host .frame, .network .network-rect').classed('selected', false); + highlight.classed('selected', true); + }, + draw_topology:function() { + var self = this; + $(self.svg_container).removeClass('noinfo'); + if (self.model.networks.length <= 0) { + $('g.network').remove(); + $(self.svg_container).addClass('noinfo'); + return; + } + + var zoom_container = d3.select(self.zoom_container); + var svg = d3.select(self.svg); + var element_properties = self.element_properties; + var network = zoom_container.selectAll('g.network') + .data(self.model.networks); + + var network_enter = network.enter() + .append('g') + .attr('class','network') + .each(function(d,i){ + this.appendChild(d3.select(self.network_tmpl).node().cloneNode(true)); + var $this = d3.select(this).select('.network-rect'); + $this + .on('mouseover',function(){ + $this.transition().style('fill', function() { + return d3.rgb(self.get_network_color(d.id)).brighter(0.5); + }); + }) + .on('mouseout',function(){ + $this.transition().style('fill', function() { + return self.get_network_color(d.id); + }); + }) + .on('click',function(){ + // Select the pnet from the list + self.add_glow(d); + self.select_network(d,true); + }); + }); + + network + .attr('id',function(d) { return 'id_net_' + d.name; }) + .attr('transform',function(d,i){ + return 'translate(' + element_properties.network_width * i + ',' + 0 + ')'; + }) + .select('.network-rect') + .attr('height', function(d) { return self.network_height; }) + .style('fill', function(d) { return self.get_network_color(d.id); }); + network + .select('.network-rect-hash') + .attr('height', function(d) { return self.network_height; }) + network + .select('.network-name') + .text(function(d) { return d.name; }); + + // Set the alarm styles for the providernet + network.each(function(d) { + if (d.alarm_level) { + d3.select(this).select('.network-rect-hash').attr('visibility','visible'); + } else { + d3.select(this).select('.network-rect-hash').attr('visibility','hidden'); + } + + d3.select(this).select('.alarm').classed('level_1 level_2 level_3 level_4',false); + d3.select(this).select('.alarm').classed('level_'+d.alarm_level,true); + }); + + $('[data-toggle="tooltip"]').tooltip({container: 'body'}); + + network.exit().remove(); + + var host = network.selectAll('g.host') + .data(function(d) { return d.hosts; }); + + var host_enter = host.enter() + .append("g") + .attr('class','host') + .each(function(d,i){ + var host_template = self['host_tmpl']; + this.appendChild(d3.select(host_template).node().cloneNode(true)); + var $this = d3.select(this); + $this + .on('mouseover',function(){ + $this.selectAll('.frame, .texts_bg, .status>circle, .alarm>circle').transition().style('stroke', function() { + return d3.rgb("#222").brighter(1.8); + }); + $this.selectAll('.texts_bg, .status>rect').transition().style('fill', function() { + return d3.rgb("#222").brighter(1.8); + }); + }) + .on('mouseout',function(){ + $this.selectAll('.frame, .texts_bg, .status>circle, .alarm>circle').transition().style('stroke', function() { + return d3.rgb("#222"); + }); + $this.selectAll('.texts_bg, .status>rect').transition().style('fill', function() { + return d3.rgb("#222"); + }); + }); + }); + + host_enter + .on('click',function(d){ + // Select the host from the list + self.add_glow(d); + self.select_host(d,true); + }); + + host + .attr('id',function(d) { return 'id_host_' + d.hostname}) + .attr('transform',function(d,i){ + return 'translate(' + element_properties.host_x + ',' + d.pos_y + ')'; + }) + .select('.frame') + .attr('height',function(d) { return d.height; }); + host + .select('.texts_bg') + .attr('y',function(d) { + return element_properties.texts_bg_y + d.height - element_properties.default_height; + }); + host + .select('.name') + .text(function(d) { return d.hostname ? self.string_truncate(d.hostname, element_properties.host_name_max_size) : "Unconfigured"; + }) + .attr('y',function(d) { + return element_properties.type_y + d.height - element_properties.default_height + element_properties.hostname_offset; + }); + host + .select('.status') + .attr('transform',function(d) { + return 'translate(' + 0 + ',' + d.height/2 + ')'; + }); + + // Logic for looks of host (status, alarms) + host.each(function(d) { + if (d.administrative == self.locked_str) { + d3.select(this).select('.status').classed('unlocked',false); + } else { + d3.select(this).select('.status').classed('unlocked',true); + } + d3.select(this).select('.alarm').classed('level_1 level_2 level_3 level_4',false); + d3.select(this).select('.alarm').classed('level_'+d.alarm_level,true); + }); + + host.exit().remove(); + + var port = host.select('g.connections') + .selectAll('g.port') + .data(function(d) {return d.connections; }); + + var port_enter = port.enter() + .append('g') + .attr('class','port') + .attr('id',function(d) { return 'id_' + d.id; }); + + port_enter + .append('line') + .attr('class','port_line'); + + port_enter + .append('text') + .attr('class','port_text'); + + host.select('g.connections').each(function(d,i){ + this._portdata = {}; + this._portdata.ports_length = d.ports.length; + this._portdata.parent_network = d.parent_network; + this._portdata.host_height = d.height; + this._portdata.port_height = d.port_height; + this._portdata.port_margin = d.port_margin; + this._portdata.lldp_heights = d.lldp_heights; + this._portdata.left = 0; + this._portdata.right = 0; + }); + + port.each(function(d,i){ + var index_diff = self.get_network_index(this.parentNode._portdata.parent_network) - + self.get_network_index(d.providernet.id); + this._index_diff = index_diff = (index_diff >= 0)? ++index_diff : index_diff; + this._direction = (this._index_diff < 0)? 'right' : 'left'; + this._index = this.parentNode._portdata[this._direction] ++; + this._lldp_label_count = d.lldp_labels.length; + }); + + port.attr('transform',function(d,index){ + var x = (this._direction === 'left') ? 0 : element_properties.host_width; + var ports_length = this.parentNode._portdata[this._direction]; + var distance = this.parentNode._portdata.port_margin; // Height of IF name + port height + if (this._direction == 'right'){ + var y = this.parentNode._portdata.port_margin; + for (i=0; i < this._index; i++){ + y += distance + this.parentNode._portdata.lldp_heights[i]; //sum previous lldp label spacings + } + } else { + var y = (this.parentNode._portdata.host_height - + (ports_length -1)*distance)/2 + this._index*distance; + } + return 'translate(' + x + ',' + y + ')'; + }); + + port + .select('.port_line') + .attr('stroke-width',function(d,i) { + return this.parentNode.parentNode._portdata.port_height; + }) + .attr('stroke', function(d, i) { + return self.get_network_color(d.providernet.id); + }) + .attr('x1',0).attr('y1',0).attr('y2',0) + .attr('x2',function(d,i) { + var parent = this.parentNode; + var width = (Math.abs(parent._index_diff) - 1)*element_properties.network_width + + element_properties.conn_width; + return (parent._direction === 'left') ? -1*width : width; + }); + + // Set connection text + port + .select('.port_text') + .attr('x',function(d) { + var parent = this.parentNode; + if (parent._direction === 'left') { + d3.select(this).classed('left',true); + return element_properties.port_text_margin.x*-1; + } else { + d3.select(this).classed('left',false); + return element_properties.port_text_margin.x; + } + }) + .attr('y',function(d) { + return element_properties.port_text_margin.y; + }) + .text(function(d) { + return d.interface.ifname; + }); + + // Set the alarmed looks for the connection + port.each(function(d) { + if (d.alarm_level) { + d3.select(this).select('.port_line').classed('port_alarmed',true); + } else { + d3.select(this).select('.port_line').classed('port_alarmed',false); + } + }); + + port + .each(function(d) { + var parent = this.parentNode; + var node = d3.select(this); + if (Math.abs(node.select('.port_line').attr('x2')) <= self.element_properties.conn_width) + cutoff = self.element_properties.lldp_name_max_size_small; + else + cutoff = self.element_properties.lldp_name_max_size; + + node.selectAll(".lldp_text").remove(); + $.each(d.lldp_labels, function(index, label) { + node.append("text") + .text(self.string_truncate(label, cutoff)) + .attr('data-toggle',function(d) { return label.length > cutoff ? 'tooltip' : null}) + .attr('data-placement',function(d) { return label.length > cutoff ? 'bottom' : null}) + .attr('title',function(d) { return label.length > cutoff ? label : null}) + .attr('class','lldp_text') + .attr('x',function(d) { + if (this.parentNode._direction === 'left') { + d3.select(this).classed('left',true); + d3.select(this).classed('right',false); + return parseInt(node.select('.port_line').attr('x2'))+element_properties.lldp_text_margin.x; + } + else { + d3.select(this).classed('left',false); + d3.select(this).classed('right',true); + return parseInt(node.select('.port_line').attr('x2'))-element_properties.lldp_text_margin.x; + } + }) + .attr('y',function(d) { + return element_properties.lldp_text_margin.y + element_properties.lldp_text_height*index; + }) + }); + }) + + $('.tooltip').remove(); + $('[data-toggle="tooltip"]').tooltip({container: 'body'}); + + port.exit().remove(); + + // Ensure label visibility is set + $('.port_text, .lldp_text').attr("visibility", self.labels); + + // Load any saved entity or zoom position or update the zoom to the starting location + var translate = horizon.cookies.get('host_topo_position'); + var selected = horizon.cookies.get('host_topo_selected'); + if (selected && self.selected_entity == null) { + $(selected).click(); + } + else if (translate) { + self.zoom.translate(translate); + self.zoomed(translate); + } + else if (!selected && !translate) { + self.zoom.translate([50,25]); + } + + // Update selected host with fresh data + refresh detail view + if (self.selected_entity && self.selected_entity.hostname){ + $.each(self.model.hosts, function(index, model_host) { + if (model_host.hostname === self.selected_entity.hostname) + self.selected_entity = model_host; + }); + } else if (self.selected_entity){ + $.each(self.model.networks, function(index, model_network) { + if (model_network.name === self.selected_entity.name) + self.selected_entity = model_network; + }); + } + self.load_detail(false); + }, + get_network_color: function(network_id) { + return this.color(this.get_network_index(network_id)); + }, + get_network_index: function(network_id) { + return this.network_index[network_id]; + }, + select_main_connection: function(connections){ + var _self = this; + var main_conn_index = 0; + var MAX_INT = 4294967295; + var min_conn_length = MAX_INT; + $.each(connections, function(index, connection){ + var conn_length = _self.sum_conn_length(connection.providernet, connections); + if(conn_length < min_conn_length){ + min_conn_length = conn_length; + main_conn_index = index; + } + }); + return connections[main_conn_index]; + }, + sum_conn_length: function(providernet, connections){ + var self = this; + var sum_conn_length = 0; + var base_index = self.get_network_index(providernet.id); + $.each(connections, function(index, connection){ + sum_conn_length += base_index - self.get_network_index(connection.providernet.id); + }); + return sum_conn_length; + }, + set_alarm_level: function(alarm, entity) { + if (alarm.severity == "critical") {entity.alarm_level = 4;} + if (alarm.severity == "major" && entity.alarm_level < 4) {entity.alarm_level = 3;} + if (alarm.severity == "minor" && entity.alarm_level < 3) {entity.alarm_level = 2;} + if (alarm.severity == "warning" && entity.alarm_level < 2) {entity.alarm_level = 1;} + }, + string_truncate: function(string, max_size) { + var self = this; + var str = string; + var suffix = self.element_properties.name_suffix; + var bytes = 0; + for (var i = 0; i < str.length; i++) { + bytes += str.charCodeAt(i) <= 255 ? 1 : 2; + if (bytes > max_size) { + str = str.substr(0, i) + suffix; + break; + } + } + return str; + } +}; diff --git a/cgcs_dashboard/version.py b/cgcs_dashboard/version.py new file mode 100755 index 00000000..66623cf9 --- /dev/null +++ b/cgcs_dashboard/version.py @@ -0,0 +1,16 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import pbr.version + + +version_info = pbr.version.VersionInfo('cgcs-dashboard') diff --git a/manage.py b/manage.py new file mode 100755 index 00000000..84acde31 --- /dev/null +++ b/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import sys + +from django.core.management import execute_from_command_line # noqa + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", + "cgcs_dashboard.test.settings") + execute_from_command_line(sys.argv) diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 00000000..3526f956 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,68 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +# Order matters to the pip dependency resolver, so sorting this file +# changes how packages are installed. New dependencies should be +# added in alphabetical order, however, some dependencies may need to +# be installed in a specific order. +# +# PBR should always appear first +pbr>=1.6 # Apache-2.0 +# Horizon Core Requirements +Babel>=2.3.4 # BSD +Django<1.9,>=1.8 # BSD +Pint>=0.5 # BSD +django-babel>=0.5.1 # BSD +django-compressor>=2.0 # MIT +django-openstack-auth>=2.4.0 # Apache-2.0 +django-pyscss>=2.0.2 # BSD License (2 clause) +httplib2>=0.7.5 # MIT +iso8601>=0.1.11 # MIT +netaddr!=0.7.16,>=0.7.13 # BSD +oslo.concurrency>=3.8.0 # Apache-2.0 +oslo.config>=3.14.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 +oslo.policy>=1.9.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 +oslo.utils>=3.16.0 # Apache-2.0 +pyScss!=1.3.5,>=1.3.4 # MIT License +python-ceilometerclient>=2.5.0 # Apache-2.0 +python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 +python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0 +python-heatclient>=1.4.0 # Apache-2.0 +python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 +python-neutronclient>=5.1.0 # Apache-2.0 +python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 +python-swiftclient>=2.2.0 # Apache-2.0 +pytz>=2013.6 # MIT +PyYAML>=3.1.0 # MIT +requests-toolbelt>=0.5 +six>=1.9.0 # MIT +XStatic>=1.0.0 # MIT License +XStatic-Angular>=1.3.7 # MIT License +XStatic-Angular-Bootstrap>=0.11.0.2 # MIT License +XStatic-Angular-FileUpload>=12.0.4.0 # MIT License +XStatic-Angular-Gettext>=2.1.0.2 # MIT License +XStatic-Angular-lrdragndrop>=1.0.2.2 # MIT License +XStatic-Angular-Schema-Form>=0.8.13.0 # MIT +XStatic-Bootstrap-Datepicker>=1.3.1.0 # Apache 2.0 License +XStatic-Bootstrap-SCSS>=3 # Apache 2.0 License +XStatic-bootswatch>=3.3.5.3 # MIT License +XStatic-D3>=3.1.6.2 # BSD License (3 clause) +XStatic-Hogan>=2.0.0.2 # Apache 2.0 License +XStatic-Font-Awesome>=4.3.0 # SIL OFL 1.1 License, MIT License +XStatic-Jasmine>=2.1.2.0 # MIT License +XStatic-jQuery>=1.7.2 # MIT License +XStatic-JQuery-Migrate>=1.2.1.1 # MIT License +XStatic-JQuery.quicksearch>=2.0.3.1 # MIT License +XStatic-JQuery.TableSorter>=2.14.5.1 # MIT License +XStatic-jquery-ui>=1.10.1 # MIT License +XStatic-JSEncrypt>=2.0.0.2 # MIT License +XStatic-mdi>=1.4.57.0 # SIL OPEN FONT LICENSE Version 1.1 +XStatic-objectpath>=1.2.1.0 # MIT +XStatic-Rickshaw>=1.5.0 # BSD License (prior) +XStatic-roboto-fontface>=0.4.3.2 # Apache 2.0 License +XStatic-smart-table!=1.4.13.0,>=1.4.5.3 # MIT License +XStatic-Spin>=1.2.5.2 # MIT License +XStatic-term.js>=0.0.4 # MIT License +XStatic-tv4>=1.2.7.0 # MIT diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..3015b1f0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,29 @@ +[metadata] +name = cgcs-dashboard +summary = CGCS Dashboard +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://docs.openstack.org/developer/horizon/ +classifier = + Environment :: OpenStack + Framework :: Django + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3.5 + +[files] +packages = + wrs_dashboard + +[build_sphinx] +all_files = 1 +build-dir = doc/build +source-dir = doc/source diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..782bb21f --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=1.8'], + pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..c016d103 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,30 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +# Order matters to the pip dependency resolver, so sorting this file +# changes how packages are installed. New dependencies should be +# added in alphabetical order, however, some dependencies may need to +# be installed in a specific order. +# +# Hacking should appear first in case something else depends on pep8 +hacking<0.11,>=0.10.0 +# +coverage>=3.6 # Apache-2.0 +django-nose>=1.4.4 # BSD +mock>=2.0 # BSD +mox3>=0.7.0 # Apache-2.0 +nodeenv>=0.9.4 # BSD License # BSD +nose # LGPL +nose-exclude # LGPL +nosehtmloutput>=0.0.3 # Apache-2.0 +nosexcover # BSD +openstack.nose-plugin>=0.7 # Apache-2.0 +oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +reno>=1.8.0 # Apache2 +python-cephclient +requests>=2.10.0 # Apache-2.0 +selenium>=2.50.1 # Apache-2.0 +sphinx!=1.3b1,<1.3,>=1.2.1 # BSD +testtools>=1.4.0 # MIT +# This also needs xvfb library installed on your OS +xvfbwrapper>=0.1.3 #license: MIT