Merge "Programmable Fabric Panel in Cisco dashboard"
This commit is contained in:
commit
9fe4a0f8b5
1
AUTHORS
1
AUTHORS
|
@ -1,2 +1,3 @@
|
|||
Rob Cresswell <robert.cresswell@outlook.com>
|
||||
Saksham Varma <sakvarma@cisco.com>
|
||||
Chirag Tayal <chiragtayal@gmail.com>
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
# under the License.
|
||||
|
||||
import ConfigParser
|
||||
import json
|
||||
import logging
|
||||
import platform
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon.utils.memoized import memoized
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
try:
|
||||
import oslo_messaging as messaging
|
||||
|
@ -30,11 +28,14 @@ try:
|
|||
except ImportError:
|
||||
from oslo.config import cfg
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon.utils.memoized import memoized
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DFAClient(object):
|
||||
"""Represents fabric enabler command line interface."""
|
||||
"""Represents Nexus Fabric Enabler RPC Client."""
|
||||
|
||||
def __init__(self):
|
||||
self.setup_client()
|
||||
|
@ -58,30 +59,144 @@ class DFAClient(object):
|
|||
|
||||
return self.clnt
|
||||
|
||||
def associate_profile_with_network(self, network):
|
||||
context = {}
|
||||
args = json.dumps(network)
|
||||
try:
|
||||
resp = self.clnt.call(context,
|
||||
'associate_profile_with_network',
|
||||
msg=args)
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request to associate profile with network"
|
||||
" failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
def get_config_profiles_detail(self):
|
||||
'''Get all config Profiles details from the Fabric Enabler'''
|
||||
@memoized
|
||||
def dfaclient():
|
||||
return DFAClient().clnt
|
||||
|
||||
context = {}
|
||||
args = json.dumps({})
|
||||
try:
|
||||
resp = self.clnt.call(context, 'get_config_profiles_detail',
|
||||
msg=args)
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for detailed Config Profiles failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
def associate_profile_with_network(network):
|
||||
'''Associate Network Profile with network'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'associate_profile_with_network',
|
||||
msg=network)
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request to associate_profile_with_network failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def do_associate_dci_id_to_project(tenant):
|
||||
'''Associate DCI ID to Project'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().cast({}, 'associate_dci_id_to_project',
|
||||
msg=tenant)
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request to associate DCI_ID to Project failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def get_fabric_summary():
|
||||
'''Get fabric details from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_fabric_summary', msg={})
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for Fabric Summary failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def get_per_config_profile_detail(cfg_profile):
|
||||
'''Get all config Profiles details from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_per_config_profile_detail',
|
||||
msg=cfg_profile)
|
||||
return resp
|
||||
except Exception as e:
|
||||
mess = (_('%(reason)s') % {"reason": e})
|
||||
LOG.error(mess)
|
||||
reason = mess.partition("Traceback")[0]
|
||||
raise exceptions.NotAvailable(reason)
|
||||
|
||||
|
||||
def get_config_profiles_detail():
|
||||
'''Get all config Profiles details from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_config_profiles_detail', msg={})
|
||||
return resp
|
||||
except Exception as e:
|
||||
mess = (_('%(reason)s') % {"reason": e})
|
||||
reason = mess.partition("Traceback")[0]
|
||||
raise exceptions.NotAvailable(reason)
|
||||
|
||||
|
||||
def get_project_details(tenant):
|
||||
'''Get project details for a tenant from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_project_detail', msg=tenant)
|
||||
if not resp:
|
||||
raise exceptions.NotFound("Project Not Found in Fabric \
|
||||
Enabler")
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for project details failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def get_network_by_tenant_id(tenant):
|
||||
'''Get all networks for a tenant from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_all_networks_for_tenant', msg=tenant)
|
||||
if resp is False:
|
||||
raise exceptions.NotFound("Project Not Found in Fabric \
|
||||
Enabler")
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for project details failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def get_instance_by_tenant_id(tenant):
|
||||
'''Get all instances for a tenant from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_instance_by_tenant_id', msg=tenant)
|
||||
if resp is False:
|
||||
raise exceptions.NotFound("Project Not Found in Fabric \
|
||||
Enabler")
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for project details failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def get_agents_details():
|
||||
'''Get all Enabler agents from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_agents_details', msg={})
|
||||
if not resp:
|
||||
raise exceptions.NotFound("No Agents found for Fabric Enabler")
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for project details failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
||||
|
||||
def get_agent_details_per_host(agent):
|
||||
'''Get Enabler agent for a host from the Fabric Enabler'''
|
||||
|
||||
try:
|
||||
resp = dfaclient().call({}, 'get_agent_details_per_host', msg=agent)
|
||||
if not resp:
|
||||
raise exceptions.NotFound("No Agent found for Fabric Enabler")
|
||||
return resp
|
||||
except (messaging.MessagingException, messaging.RemoteError,
|
||||
messaging.MessagingTimeout):
|
||||
LOG.error("RPC: Request for project details failed.")
|
||||
raise exceptions.NotAvailable("RPC to Fabric Enabler failed")
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# 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 horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon_cisco_ui.cisco.dfa import dfa_client
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssociateDCI(forms.SelfHandlingForm):
|
||||
project_id = forms.CharField(label=_("Project ID "),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
dci_id = forms.IntegerField(label=_("DCI ID"), min_value=1,
|
||||
max_value=1600000)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.debug('request = %(req)s, params = %(params)s',
|
||||
{'req': request, 'params': data})
|
||||
tenant = dict(tenant_id=request.user.project_id,
|
||||
tenant_name=request.user.project_name,
|
||||
dci_id=data['dci_id'])
|
||||
dfa_client.do_associate_dci_id_to_project(tenant)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:cisco:dfa:index')
|
||||
msg = _('Failed to associate DCI ID to project')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DisassociateDCI(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.debug('request = %(req)s, params = %(params)s',
|
||||
{'req': request, 'params': data})
|
||||
tenant = dict(tenant_id=request.user.project_id,
|
||||
tenant_name=request.user.project_name,
|
||||
dci_id=0)
|
||||
dfa_client.do_associate_dci_id_to_project(tenant)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:cisco:dfa:index')
|
||||
msg = _('Failed to disassociate DCI ID from project')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
return True
|
|
@ -0,0 +1,43 @@
|
|||
# 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 django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
from horizon_cisco_ui.cisco import dashboard
|
||||
import logging
|
||||
import os.path
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DFA(horizon.Panel):
|
||||
name = _("Programmable Fabric")
|
||||
slug = "dfa"
|
||||
permissions = ('openstack.services.network',)
|
||||
|
||||
def allowed(self, context):
|
||||
request = context['request']
|
||||
if not request.user.has_perms(self.permissions):
|
||||
return False
|
||||
try:
|
||||
if not os.path.isfile('/etc/saf/enabler_conf.ini'):
|
||||
return False
|
||||
except Exception:
|
||||
LOG.error("Exception occured trying to find the Nexus Fabric "
|
||||
"Enabler Configuration File")
|
||||
return False
|
||||
if not super(DFA, self).allowed(context):
|
||||
return False
|
||||
return True
|
||||
|
||||
dashboard.Cisco.register(DFA)
|
|
@ -0,0 +1,234 @@
|
|||
# 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 django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssociateDCIAction(tables.LinkAction):
|
||||
name = "associate"
|
||||
verbose_name = _("Associate DCI ID")
|
||||
url = "horizon:cisco:dfa:associate"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "link"
|
||||
|
||||
|
||||
class DisassociateDCIAction(tables.LinkAction):
|
||||
name = "disassociate"
|
||||
verbose_name = _("Disassociate DCI ID")
|
||||
url = "horizon:cisco:dfa:disassociate"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "link"
|
||||
|
||||
|
||||
class SearchFilterAction(tables.FilterAction):
|
||||
name = "searchfilter"
|
||||
|
||||
|
||||
def get_vdp(obj):
|
||||
if obj.get('vdp_vlan') > 0:
|
||||
return obj.get('vdp_vlan')
|
||||
|
||||
if 'reason' in obj:
|
||||
template_name = 'cisco/dfa/_vdp_reason.html'
|
||||
context = {
|
||||
"vdp": obj.get('vdp_vlan'),
|
||||
"id": obj.get('port_id'),
|
||||
"reason": obj.get('reason')
|
||||
}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
return obj.get('vdp_vlan')
|
||||
|
||||
|
||||
def get_result(obj):
|
||||
if obj.get('result') == 'SUCCESS':
|
||||
return obj.get('result')
|
||||
|
||||
if 'network_id' in obj:
|
||||
obj_id = obj.get('network_id')
|
||||
elif 'project_id' in obj:
|
||||
obj_id = obj.get('project_id')
|
||||
elif 'port_id' in obj:
|
||||
obj_id = obj.get('port_id')
|
||||
else:
|
||||
obj_id = 'none'
|
||||
|
||||
if 'reason' in obj:
|
||||
template_name = 'cisco/dfa/_reason.html'
|
||||
context = {
|
||||
"result": obj.get('result'),
|
||||
"id": obj_id,
|
||||
"reason": obj.get('reason')
|
||||
}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
return obj.get('result')
|
||||
|
||||
|
||||
def get_instance_info(obj):
|
||||
if 'instance_id' and 'veth_intf' in obj:
|
||||
template_name = 'cisco/dfa/_instance.html'
|
||||
context = {
|
||||
"name": obj.get('name'),
|
||||
"nw_name": obj.get('network_name'),
|
||||
"id": ''.join(e for e in obj.get('instance_id') if e.isalnum()),
|
||||
"mac": obj.get('mac'),
|
||||
"ip": obj.get('ip'),
|
||||
"port": obj.get('port_id'),
|
||||
"host_port": ', '.join((obj.get('host'), obj.get('veth_intf'))),
|
||||
"TOR_port": ', '.join((obj.get('remote_system_name'),
|
||||
obj.get('remote_port')))
|
||||
}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
return obj.get('name')
|
||||
|
||||
|
||||
class FabricSummaryTable(tables.DataTable):
|
||||
key = tables.Column("key", sortable=False,
|
||||
verbose_name=_("Fabric Property"))
|
||||
value = tables.Column("value",
|
||||
verbose_name=_("Value"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.get('key')
|
||||
|
||||
class Meta(object):
|
||||
name = "fabricsummary"
|
||||
verbose_name = _("Fabric Summary")
|
||||
table_actions = (SearchFilterAction, )
|
||||
multi_select = False
|
||||
|
||||
|
||||
class CFGProfileTable(tables.DataTable):
|
||||
profile_name = tables.Column("profileName", sortable=False,
|
||||
verbose_name=_("Profile Name"),
|
||||
link='horizon:cisco:dfa:detailprofile')
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return ':'.join((obj.get('profileName'), obj.get('profileType')))
|
||||
|
||||
class Meta(object):
|
||||
name = "cfgprofile"
|
||||
verbose_name = _("CFGProfile")
|
||||
table_actions = (SearchFilterAction, )
|
||||
multi_select = False
|
||||
|
||||
|
||||
class ProjectTable(tables.DataTable):
|
||||
project_name = tables.Column("project_name",
|
||||
verbose_name=_("Project Name"))
|
||||
project_id = tables.Column("project_id", verbose_name=_("Project ID"))
|
||||
seg_id = tables.Column("seg_id", verbose_name=_("L3 VNI"))
|
||||
dci_id = tables.Column("dci_id", verbose_name=_("DCI ID"))
|
||||
result = tables.Column(get_result, verbose_name=_("Result"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.get('project_id')
|
||||
|
||||
class Meta(object):
|
||||
name = "projecttable"
|
||||
hidden_title = False
|
||||
verbose_name = _("Projects")
|
||||
row_actions = (AssociateDCIAction, DisassociateDCIAction, )
|
||||
|
||||
|
||||
class NetworkTable(tables.DataTable):
|
||||
network_name = tables.Column("network_name", verbose_name=_("Name"))
|
||||
network_id = tables.Column("network_id", verbose_name=_("ID"))
|
||||
config_profile = tables.Column("config_profile",
|
||||
verbose_name=_("Network Profile"))
|
||||
seg_id = tables.Column("seg_id", verbose_name=_("Segmentation ID"))
|
||||
vlan_id = tables.Column("vlan_id", verbose_name=_("Vlan ID"))
|
||||
result = tables.Column(get_result, verbose_name=_("Result"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.get('network_id')
|
||||
|
||||
class Meta(object):
|
||||
name = "networktable"
|
||||
hidden_title = False
|
||||
verbose_name = _("Networks")
|
||||
table_actions = (SearchFilterAction,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class InstanceTable(tables.DataTable):
|
||||
instance_name = tables.Column(get_instance_info, verbose_name=_("Name"))
|
||||
host = tables.Column("host", verbose_name=_("Host"))
|
||||
tor = tables.Column("remote_system_name", verbose_name=_("TOR"))
|
||||
network_name = tables.Column("network_name",
|
||||
verbose_name=_("Network Name"))
|
||||
local_vlan = tables.Column("local_vlan", verbose_name=_("Local Vlan"))
|
||||
vdp_vlan = tables.Column(get_vdp, verbose_name=_("Link Local Vlan"))
|
||||
seg_id = tables.Column("seg_id", verbose_name=_("Segmentation ID"))
|
||||
result = tables.Column(get_result, verbose_name=_("Result"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.get('port_id')
|
||||
|
||||
class Meta(object):
|
||||
name = "instancetable"
|
||||
hidden_title = False
|
||||
verbose_name = _("Instances")
|
||||
table_actions = (SearchFilterAction,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class AgentsTable(tables.DataTable):
|
||||
host = tables.Column("host", verbose_name=_("Host"),
|
||||
link='horizon:cisco:dfa:detail')
|
||||
created = tables.Column("created", verbose_name=_("Created"))
|
||||
heartbeat = tables.Column("heartbeat", verbose_name=_("Heartbeat"))
|
||||
agent_status = tables.Column("agent_status",
|
||||
verbose_name=_("Agent Status"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.get('host')
|
||||
|
||||
class Meta(object):
|
||||
name = "agentstable"
|
||||
verbose_name = _("Agents Table")
|
||||
table_actions = (SearchFilterAction,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class TopologyTable(tables.DataTable):
|
||||
interface = tables.Column("interface", verbose_name=_("Interface"))
|
||||
remote_port = tables.Column("remote_port", verbose_name=_("Remote Port"))
|
||||
bond_intf = tables.Column("bond_intf", verbose_name=_("Bond Interface"))
|
||||
remote_evb_cfgd = tables.Column("remote_evb_cfgd",
|
||||
verbose_name=_("Remote EVB Configured"))
|
||||
remote_system_desc = tables.Column("remote_system_desc",
|
||||
verbose_name=_("Remote System"))
|
||||
remote_chassis_mac = tables.Column("remote_chassis_mac",
|
||||
verbose_name=_("Remote Chassis Mac"))
|
||||
remote_mgmt_addr = tables.Column("remote_mgmt_addr",
|
||||
verbose_name=_("Remote Mgmt Address"))
|
||||
remote_system_name = tables.Column("remote_system_name",
|
||||
verbose_name=_("Remote Sys Name"))
|
||||
remote_evb_mode = tables.Column("remote_evb_mode",
|
||||
verbose_name=_("Remote EVB Mode"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj.get('interface')
|
||||
|
||||
class Meta(object):
|
||||
name = "topology"
|
||||
hidden_title = False
|
||||
verbose_name = _("Topology")
|
||||
multi_select = False
|
|
@ -0,0 +1,133 @@
|
|||
# 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 dateutil.parser
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from horizon_cisco_ui.cisco.dfa import dfa_client
|
||||
from horizon_cisco_ui.cisco.dfa import tables
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FabricSummaryTab(tabs.TableTab):
|
||||
name = _("Fabric Summary")
|
||||
slug = "fabric_summary_tab"
|
||||
table_classes = (tables.FabricSummaryTable,)
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def get_fabricsummary_data(self):
|
||||
summary = []
|
||||
try:
|
||||
summary = dfa_client.get_fabric_summary()
|
||||
except Exception as exc:
|
||||
exceptions.handle(self.request, exc.message)
|
||||
return summary
|
||||
|
||||
|
||||
class CFGProfileTab(tabs.TableTab):
|
||||
name = _("Network Profiles")
|
||||
slug = "cfgprofile_tab"
|
||||
table_classes = (tables.CFGProfileTable,)
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def get_cfgprofile_data(self):
|
||||
try:
|
||||
cfgplist = dfa_client.get_config_profiles_detail()
|
||||
profile_list = [p for p in cfgplist
|
||||
if (p.get('profileSubType') ==
|
||||
'network:universal')]
|
||||
except Exception as exc:
|
||||
profile_list = []
|
||||
exceptions.handle(self.request, exc.message)
|
||||
return profile_list
|
||||
|
||||
|
||||
class NFEInfoTab(tabs.TableTab):
|
||||
|
||||
name = _("Fabric View")
|
||||
slug = "nfe_info_tab"
|
||||
table_classes = (tables.ProjectTable, tables.NetworkTable,
|
||||
tables.InstanceTable, )
|
||||
template_name = 'cisco/dfa/enablerinfo_tables.html'
|
||||
|
||||
def get_projecttable_data(self):
|
||||
try:
|
||||
tenant = dict(tenant_id=self.request.user.project_id)
|
||||
project_list = dfa_client.get_project_details(tenant)
|
||||
except Exception as exc:
|
||||
project_list = []
|
||||
exceptions.handle(self.request, exc.message)
|
||||
return project_list
|
||||
|
||||
def get_networktable_data(self):
|
||||
try:
|
||||
tenant = dict(tenant_id=self.request.user.project_id)
|
||||
netlist = dfa_client.get_network_by_tenant_id(tenant)
|
||||
except Exception as exc:
|
||||
netlist = []
|
||||
exceptions.handle(self.request, exc.message)
|
||||
return netlist
|
||||
|
||||
def get_instancetable_data(self):
|
||||
try:
|
||||
tenant = dict(tenant_id=self.request.user.project_id)
|
||||
instance_list = dfa_client.get_instance_by_tenant_id(tenant)
|
||||
agent_list = dfa_client.get_agents_details()
|
||||
port = {}
|
||||
for agent in agent_list:
|
||||
cfg = json.loads(agent.get('config'))
|
||||
topo = cfg.get('topo')
|
||||
intf = topo.get(cfg.get('veth_intf'))
|
||||
host = agent.get('host')
|
||||
if not intf:
|
||||
continue
|
||||
port.update({host: dict(veth_intf=cfg.get('uplink'),
|
||||
remote_system_name=intf.get('remote_system_name'),
|
||||
remote_port=intf.get('remote_port'))})
|
||||
if port:
|
||||
for instance in instance_list:
|
||||
instance.update(port.get(instance.get('host')))
|
||||
except Exception as exc:
|
||||
instance_list = []
|
||||
exceptions.handle(self.request, exc.message)
|
||||
return instance_list
|
||||
|
||||
|
||||
class NFEAgentTab(tabs.TableTab):
|
||||
name = _("Topology View")
|
||||
slug = "nfe_agents_tab"
|
||||
table_classes = (tables.AgentsTable,)
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
|
||||
def get_agentstable_data(self):
|
||||
try:
|
||||
agent_list = dfa_client.get_agents_details()
|
||||
for agent in agent_list:
|
||||
agent["heartbeat"] = dateutil.parser.parse(
|
||||
agent.get('heartbeat'))
|
||||
agent["created"] = dateutil.parser.parse(agent.get('created'))
|
||||
except Exception as exc:
|
||||
agent_list = []
|
||||
exceptions.handle(self.request, exc.message)
|
||||
return agent_list
|
||||
|
||||
|
||||
class DFATabs(tabs.TabGroup):
|
||||
slug = "dfa_tabs"
|
||||
tabs = (FabricSummaryTab, CFGProfileTab, NFEInfoTab, NFEAgentTab)
|
||||
sticky = True
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p><strong>{% trans "Data Center Interconnect:" %}</strong> {% blocktrans %} Cisco DCI solutions extend LAN and SAN connectivity across geographically dispersed active data centers.{% endblocktrans %}</p>
|
||||
<p><strong>{% trans "DCI ID:" %}</strong> {% blocktrans %} 1 - 1600000{% endblocktrans %}</p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Host Name" %}</dt>
|
||||
<dd>{{ agent.host }}</dd>
|
||||
<dt>{% trans "Created At" %}</dt>
|
||||
<dd>{{ agent.created|parse_date }}</dd>
|
||||
<dt>{% trans "Heartbeat" %}</dt>
|
||||
<dd>{{ agent.heartbeat|parse_date }}</dd>
|
||||
<dt>{% trans "Uplink" %}</dt>
|
||||
<dd>{{ uplink |default:_("None")}}</dd>
|
||||
<dt>{% trans "veth Interface" %}</dt>
|
||||
<dd>{{ veth_intf |default:_("None")}}</dd>
|
||||
<dt>{% trans "Member Ports" %}</dt>
|
||||
<dd>{{ memb_ports |default:_("None")}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{% load i18n %}
|
||||
|
||||
<div class="detail">
|
||||
<p> {% trans Network Profile is an autoconfiguration template consisting of collection of commands which instantiates day-1 tenant-related configurations on CISCO Nexus switches.<br> Profiles can be added or modified only from DCNM %}</p><br>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Profile Name: " %}</dt>
|
||||
<dd>{{ Profile_Name }}</dd>
|
||||
<dt>{% trans "Profile Type: " %}</dt>
|
||||
<dd>{{ Profile_Type }}</dd>
|
||||
<dt>{% trans "Forwarding Mode: " %}</dt>
|
||||
<dd>{{ fwding_mode }}</dd>
|
||||
<dt>{% trans "Description: " %}</dt>
|
||||
<dd>{{ description }}</dd><br>
|
||||
<dt>{% trans "Commands: " %}</dt>
|
||||
<dd>{% autoescape off %}{{ commands }}{% endautoescape %}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body%}
|
||||
<p>{% blocktrans %}Are you sure to dis-associate DCI ID to Project?{% endblocktrans %}</p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% load i18n %}
|
||||
<a href="#" id="instance_{{ id }}_{{ nw_name }}" class="link-popover" rel="popover" tabindex="0" data-placement="right" data-trigger="focus" data-content="
|
||||
<table class='table table-bordered'>
|
||||
<tr><th>{% trans 'IP' %}</th><td>{{ ip }}</td></tr>
|
||||
<tr><th>{% trans 'MAC' %}</th><td>{{ mac }}</td></tr>
|
||||
<tr><th>{% trans 'PORT' %}</th><td>{{ port }}</td></tr>
|
||||
<tr><th>{% trans 'HOST, Port' %}</th><td>{{ host_port }}</td></tr>
|
||||
<tr><th>{% trans 'TOR, Port' %}</th><td>{{ TOR_port }}</td></tr>
|
||||
</table>
|
||||
" data-original-title="{% blocktrans %}Instance Details: {{ name }}">{{ name }}{% endblocktrans %}</a>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(function () {
|
||||
var $instance = $("#instance_{{ id }}_{{ nw_name }}");
|
||||
if ( $instance.popover ) {
|
||||
$instance.popover({html:true});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}
|
||||
<a href="#" data-toggle="tooltip" data-placement="right" data-trigger="focus" data-original-title="
|
||||
{{ description }}
|
||||
">{{ name }}</a>
|
|
@ -0,0 +1,14 @@
|
|||
{% load i18n %}
|
||||
<a href="#" id="result_{{ result }}_{{ id }}" class="link-popover" rel="popover" tabindex="0" data-placement="left" data-trigger="focus" data-content="
|
||||
<table class='table table-bordered'>
|
||||
<tr><th>{% trans 'Reason' %}</th><td>{{ reason }}</td></tr>
|
||||
</table>
|
||||
" data-original-title="{% blocktrans %}">{{ result }}{% endblocktrans %}</a>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(function () {
|
||||
var $result = $("#result_{{ result }}_{{ id }}");
|
||||
if ( $result.popover ) {
|
||||
$result.popover({html:true});
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Associate DCI ID to Project" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "cisco/dfa/_associate.html" %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,16 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Fabric Enabler Hosts"%}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% include "cisco/dfa/_detail_overview.html" %}
|
||||
<hr>
|
||||
<div id="topology">
|
||||
{{ topology_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Network Profiles"%}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% include "cisco/dfa/_detail_profile_overview.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Disassociate DCI ID from Project" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "cisco/dfa/_disassociate.html" %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
|||
{% block main %}
|
||||
<div id="project_id">
|
||||
{{ projecttable_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="network">
|
||||
{{ networktable_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="instance">
|
||||
{{ instancetable_table.render }}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "DFA" %} {% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,3 +1,6 @@
|
|||
# Copyright 2014 Cisco Systems, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
|
@ -10,7 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import mock
|
||||
import platform
|
||||
|
||||
|
@ -38,12 +40,13 @@ class DFAClientTestCase(test.BaseAdminViewTests):
|
|||
name='net1',
|
||||
tenant_id=1)
|
||||
|
||||
message = json.dumps(network)
|
||||
with mock.patch.object(self.client.clnt, 'call') as mock_call:
|
||||
self.client.associate_profile_with_network(network)
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
dfa_client.associate_profile_with_network(network)
|
||||
|
||||
mock_call.assert_called_with({}, 'associate_profile_with_network',
|
||||
msg=message)
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'associate_profile_with_network', msg=network)
|
||||
|
||||
def test_associate_profile_with_network_rpc_exception(self):
|
||||
network = dict(id='1125-as45-afg5f-3457',
|
||||
|
@ -51,10 +54,97 @@ class DFAClientTestCase(test.BaseAdminViewTests):
|
|||
name='net1',
|
||||
tenant_id=1)
|
||||
|
||||
with mock.patch.object(self.client.clnt, 'call',
|
||||
side_effect=dfa_client.messaging.MessagingException), \
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as mock_client, \
|
||||
self.assertRaises(dfa_client.exceptions.NotAvailable) as cm:
|
||||
self.client.associate_profile_with_network(network)
|
||||
mock_client.return_value.call = mock.MagicMock(
|
||||
side_effect=dfa_client.messaging.MessagingException)
|
||||
dfa_client.associate_profile_with_network(network)
|
||||
|
||||
self.assertEqual('RPC to Fabric Enabler failed',
|
||||
str(cm.exception))
|
||||
|
||||
def test_do_associate_dci_id_to_project(self):
|
||||
tenant = dict(tenant_id=1,
|
||||
tenant_name='Project1',
|
||||
dci_id=1001)
|
||||
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.cast = mock.MagicMock()
|
||||
dfa_client.do_associate_dci_id_to_project(tenant)
|
||||
|
||||
mock_client.return_value.cast.assert_called_with(
|
||||
{}, 'associate_dci_id_to_project', msg=tenant)
|
||||
|
||||
def test_get_fabric_summary(self):
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_fabric_summary()
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_fabric_summary', msg={})
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
||||
def test_get_config_profiles_detail(self):
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_config_profiles_detail()
|
||||
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_config_profiles_detail', msg={})
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
||||
def test_get_project_details(self):
|
||||
tenant = dict(tenant_id=1)
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_project_details(tenant)
|
||||
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_project_detail', msg=tenant)
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
||||
def test_get_network_by_tenant_id(self):
|
||||
tenant = dict(tenant_id=1)
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_network_by_tenant_id(tenant)
|
||||
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_all_networks_for_tenant', msg=tenant)
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
||||
def test_get_instance_by_tenant_id(self):
|
||||
tenant = dict(tenant_id=1)
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_instance_by_tenant_id(tenant)
|
||||
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_instance_by_tenant_id', msg=tenant)
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
||||
def test_get_agents_details(self):
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_agents_details()
|
||||
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_agents_details', msg={})
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
||||
def test_get_agent_details_per_host(self):
|
||||
agent = dict(tenant_id=1)
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.dfa_client.dfaclient') as \
|
||||
mock_client:
|
||||
mock_client.return_value.call = mock.MagicMock()
|
||||
resp = dfa_client.get_agent_details_per_host(agent)
|
||||
|
||||
mock_client.return_value.call.assert_called_with(
|
||||
{}, 'get_agent_details_per_host', msg=agent)
|
||||
self.assertEqual(resp, mock_client.return_value.call.return_value)
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
# 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 django.core.urlresolvers import reverse
|
||||
|
||||
from horizon_cisco_ui.cisco.dfa.tabs import dfa_client as dc
|
||||
from horizon_cisco_ui.cisco.dfa.test import test_data
|
||||
|
||||
import mock
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
class DFATestCase(test.BaseAdminViewTests):
|
||||
|
||||
def setUp(self):
|
||||
super(DFATestCase, self).setUp()
|
||||
self.dfa_client = dc.DFAClient
|
||||
|
||||
def _setup_test_data(self):
|
||||
super(DFATestCase, self)._setup_test_data()
|
||||
test_data.data(self)
|
||||
|
||||
def _test_base_index(self):
|
||||
profiles = self.dfa_config_profile.list()
|
||||
projects = self.dfa_project.list()
|
||||
networks = self.dfa_network.list()
|
||||
instances = self.dfa_instance.list()
|
||||
agents = self.dfa_agent.list()
|
||||
summary = self.dfa_summary.list()[0]
|
||||
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_config_profiles_detail', return_value=profiles), \
|
||||
mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_network_by_tenant_id',
|
||||
return_value=networks), \
|
||||
mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_instance_by_tenant_id',
|
||||
return_value=instances), \
|
||||
mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_project_details',
|
||||
return_value=projects), \
|
||||
mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_fabric_summary',
|
||||
return_value=summary), \
|
||||
mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_agents_details',
|
||||
return_value=agents):
|
||||
res = self.client.get(reverse('horizon:cisco:dfa:index'))
|
||||
|
||||
self.assertTemplateUsed(res, 'cisco/dfa/index.html')
|
||||
|
||||
return res
|
||||
|
||||
def test_fabric_summary_tab_index(self):
|
||||
res = self._test_base_index()
|
||||
services_tab = res.context['tab_group'].get_tab('fabric_summary_tab')
|
||||
self.assertEqual(
|
||||
services_tab._tables['fabricsummary'].data,
|
||||
[summary for summary in self.dfa_summary.list()[0]])
|
||||
|
||||
def test_cfg_profile_index(self):
|
||||
res = self._test_base_index()
|
||||
services_tab = res.context['tab_group'].get_tab('cfgprofile_tab')
|
||||
self.assertEqual(
|
||||
services_tab._tables['cfgprofile'].data,
|
||||
[profiles for profiles in self.dfa_config_profile.list()])
|
||||
|
||||
def test_nfe_info_index(self):
|
||||
res = self._test_base_index()
|
||||
services_tab = res.context['tab_group'].get_tab('nfe_info_tab')
|
||||
self.assertEqual(
|
||||
services_tab._tables['projecttable'].data,
|
||||
[project for project in self.dfa_project.list()])
|
||||
|
||||
self.assertEqual(
|
||||
services_tab._tables['networktable'].data,
|
||||
[network for network in self.dfa_network.list()])
|
||||
|
||||
def test_nfe_agents_index(self):
|
||||
res = self._test_base_index()
|
||||
services_tab = res.context['tab_group'].get_tab('nfe_agents_tab')
|
||||
self.assertEqual(
|
||||
services_tab._tables['agentstable'].data,
|
||||
[agent for agent in self.dfa_agent.list()])
|
||||
|
||||
def test_agent_detail(self):
|
||||
agent = self.dfa_agent.list()
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_agent_details_per_host',
|
||||
return_value=agent):
|
||||
res = self.client.get(reverse('horizon:cisco:dfa:detail',
|
||||
args=['compute0']))
|
||||
self.assertTemplateUsed(res, 'cisco/dfa/detail.html')
|
||||
|
||||
def test_agent_detail_exception(self):
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.get_agent_details_per_host',
|
||||
side_effect=dc.exceptions.NotAvailable):
|
||||
url = reverse('horizon:cisco:dfa:detail', args=['compute0'])
|
||||
res = self.client.get(url)
|
||||
|
||||
redir_url = reverse('horizon:cisco:dfa:index')
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
def test_associate_dci_to_project(self):
|
||||
formdata = {'project_id': 123456, 'dci_id': 1001}
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.do_associate_dci_id_to_project'):
|
||||
url = reverse('horizon:cisco:dfa:associate', args=['123456'])
|
||||
res = self.client.post(url, formdata)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, '/cisco/dfa/')
|
||||
|
||||
def test_disassociate_dci_to_project(self):
|
||||
formdata = {'project_id': 123456, 'dci_id': 0}
|
||||
with mock.patch('horizon_cisco_ui.cisco.dfa.tabs.dfa_client'
|
||||
'.do_associate_dci_id_to_project'):
|
||||
url = reverse('horizon:cisco:dfa:disassociate', args=['123456'])
|
||||
res = self.client.post(url, formdata)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, '/cisco/dfa/')
|
|
@ -0,0 +1,98 @@
|
|||
# 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.
|
||||
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
|
||||
|
||||
def data(TEST):
|
||||
# Test DataContainers for DFA Workflow
|
||||
TEST.dfa_precreate_network = utils.TestDataContainer()
|
||||
TEST.dfa_config_profile = utils.TestDataContainer()
|
||||
TEST.dfa_project = utils.TestDataContainer()
|
||||
TEST.dfa_network = utils.TestDataContainer()
|
||||
TEST.dfa_instance = utils.TestDataContainer()
|
||||
TEST.dfa_agent = utils.TestDataContainer()
|
||||
TEST.dfa_summary = utils.TestDataContainer()
|
||||
|
||||
precreate_dict = {'tenant_id': '1',
|
||||
'nwk_name': 'net1',
|
||||
'subnet': '10.0.0.0/24',
|
||||
'cfgp': 'defaultNetworkL2Profile'}
|
||||
TEST.dfa_precreate_network.add(precreate_dict)
|
||||
|
||||
cfg_profile_dict = {'profileSubType': 'network:universal',
|
||||
'description': 'native dhcp EF Profile',
|
||||
'editable': 'yes',
|
||||
'configCommands': 'vlan $vlanId',
|
||||
'profileType': 'IPBD',
|
||||
'profileName': 'nativeDhcpEfProfile',
|
||||
'forwardingMode': 'proxy-gateway',
|
||||
'modifyTimestamp': 'Wed Mar 30 212631 PDT 2016'}
|
||||
TEST.dfa_config_profile.add(cfg_profile_dict)
|
||||
|
||||
project_dict = {'project_id': 'cc073fa35b544e27b6a7802e9919afb1',
|
||||
'dci_id': 12344,
|
||||
'project_name': 'Cisco',
|
||||
'result': 'SUCCESS'}
|
||||
TEST.dfa_project.add(project_dict)
|
||||
|
||||
network_dict = {'network_id': '05df940e-7a68-4ead-9618-ae199c4dc289',
|
||||
'reason': 'Request to DCNM failed: [500] Segment ID: \
|
||||
76388 already exists.',
|
||||
'seg_id': 76388,
|
||||
'result': 'CREATE_FAIL',
|
||||
'config_profile': 'defaultNetworkL2Profile',
|
||||
'network_name': 'net2',
|
||||
'vlan_id': None}
|
||||
TEST.dfa_network.add(network_dict)
|
||||
|
||||
network_dict = {'network_id': '7c67bc14-8b7b-44ea-9ed5-c3fb36d7bfd9',
|
||||
'reason': None,
|
||||
'seg_id': 76377,
|
||||
'result': 'SUCCESS',
|
||||
'config_profile': 'defaultNetworkUniversalEfProfile',
|
||||
'network_name': 'network1',
|
||||
'vlan_id': 10}
|
||||
TEST.dfa_network.add(network_dict)
|
||||
|
||||
instance_dict = {'local_vlan': 10,
|
||||
'name': 'INS1',
|
||||
'network_id': '7c67bc14-8b7b-44ea-9ed5-c3fb36d7bfd9',
|
||||
'instance_id': 'e7cd03bed37b4de48f19fc5232056025',
|
||||
'host': 'ctayal-openstack',
|
||||
'veth_intf': 'eth1',
|
||||
'remote_system_name': 'N6k-75',
|
||||
'remote_port': 'Ethernet1/47',
|
||||
'seg_id': 76377,
|
||||
'result': 'SUCCESS',
|
||||
'vdp_vlan': 110,
|
||||
'port_id': '6672d670-9ba9-4889-8754-367bd51165fd'}
|
||||
TEST.dfa_instance.add(instance_dict)
|
||||
|
||||
agent_dict = {'heartbeat': '2016-09-01T04:40:17.000000',
|
||||
'host': 'compute0',
|
||||
'config': '{"memb_ports": null, "veth_intf": "", \
|
||||
"uplink": ""}',
|
||||
'created': u'2016-07-27T06:34:48.000000'}
|
||||
TEST.dfa_agent.add(agent_dict)
|
||||
|
||||
summary_dict = [{u'value': 2.0, u'key': u'Fabric Enabler Version'},
|
||||
{u'value': u'10.0(1)', u'key': u'DCNM Version'},
|
||||
{u'value': u'172.28.11.151', u'key': u'DCNM IP'},
|
||||
{u'value': u'n6k', u'key': u'Switch Type'},
|
||||
{u'value': u'fabricpath', u'key': u'Fabric Type'},
|
||||
{u'value': u'2', u'key': u'Fabric ID'},
|
||||
{u'value': u'76345-76545', u'key': u'Segment ID Range'}]
|
||||
|
||||
TEST.dfa_summary.add(summary_dict)
|
|
@ -0,0 +1,33 @@
|
|||
# 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 django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from horizon_cisco_ui.cisco.dfa import views
|
||||
|
||||
AGENTS = r'^(?P<host>[^/]+)/%s$'
|
||||
PROJECT = r'^(?P<project_id>[^/]+)/%s$'
|
||||
PROFILE = r'^(?P<profile_name>[^/]+)/%s$'
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(AGENTS % 'detail', views.DetailView.as_view(), name='detail'),
|
||||
url(PROFILE % 'detailprofile', views.DetailProfileView.as_view(),
|
||||
name='detailprofile'),
|
||||
url(PROJECT % 'associate', views.AssociateDCIToProjectView.as_view(),
|
||||
name='associate'),
|
||||
url(PROJECT % 'disassociate', views.DisssociateDCIToProjectView.as_view(),
|
||||
name='disassociate'),
|
||||
)
|
|
@ -0,0 +1,191 @@
|
|||
# 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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
from horizon_cisco_ui.cisco.dfa import dfa_client
|
||||
from horizon_cisco_ui.cisco.dfa import forms as dfaforms
|
||||
from horizon_cisco_ui.cisco.dfa import tables as dfatables
|
||||
from horizon_cisco_ui.cisco.dfa import tabs as dfatab
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
tab_group_class = dfatab.DFATabs
|
||||
template_name = 'cisco/dfa/index.html'
|
||||
page_title = _("Programmable Fabric")
|
||||
|
||||
|
||||
class AssociateDCIToProjectView(forms.ModalFormView):
|
||||
form_class = dfaforms.AssociateDCI
|
||||
form_id = "associate_form"
|
||||
modal_header = _("Associate DCI ID to Project")
|
||||
template_name = 'cisco/dfa/associate.html'
|
||||
submit_label = _("ASSOCIATE")
|
||||
submit_url = "horizon:cisco:dfa:associate"
|
||||
success_url = reverse_lazy('horizon:cisco:dfa:index')
|
||||
page_title = _("ASSOCIATE DCI ID")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AssociateDCIToProjectView,
|
||||
self).get_context_data(**kwargs)
|
||||
args = (self.kwargs['project_id'],)
|
||||
context["project_id"] = self.kwargs['project_id']
|
||||
context["submit_url"] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
return self.kwargs["project_id"]
|
||||
|
||||
def get_initial(self):
|
||||
return {'project_id': self._get_object()}
|
||||
|
||||
|
||||
class DisssociateDCIToProjectView(forms.ModalFormView):
|
||||
form_class = dfaforms.DisassociateDCI
|
||||
form_id = "disassociate_form"
|
||||
modal_header = _("Disassociate DCI ID to Project")
|
||||
template_name = 'cisco/dfa/disassociate.html'
|
||||
submit_label = _("DISASSOCIATE")
|
||||
submit_url = "horizon:cisco:dfa:disassociate"
|
||||
success_url = reverse_lazy('horizon:cisco:dfa:index')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DisssociateDCIToProjectView,
|
||||
self).get_context_data(**kwargs)
|
||||
args = (self.kwargs['project_id'],)
|
||||
context["project_id"] = self.kwargs['project_id']
|
||||
context["submit_url"] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
return self.kwargs["project_id"]
|
||||
|
||||
def get_initial(self):
|
||||
return {'project_id': self._get_object()}
|
||||
|
||||
|
||||
class DetailProfileView(views.HorizonTemplateView):
|
||||
template_name = 'cisco/dfa/detailprofile.html'
|
||||
page_title = "{{ profile_name }}"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailProfileView, self).get_context_data(**kwargs)
|
||||
data = self._get_data()
|
||||
rep = {'\r': '<br>', ' ': ' '}
|
||||
rep = dict((re.escape(k), v) for k, v in rep.iteritems())
|
||||
pattern = re.compile("|".join(rep.keys()))
|
||||
commands = pattern.sub(lambda m: rep[re.escape(m.group(0))],
|
||||
data.get('configCommands'))
|
||||
context['Profile_Name'] = self.kwargs['profile_name'].split(":")[0]
|
||||
context['Profile_Type'] = self.kwargs['profile_name'].split(":")[1]
|
||||
context['fwding_mode'] = data.get('forwardingMode')
|
||||
context['description'] = data.get('description')
|
||||
context['commands'] = commands
|
||||
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
profile_name = self.kwargs['profile_name']
|
||||
cfg_profile = {'profile': profile_name.split(":")[0],
|
||||
'ftype': profile_name.split(":")[1]}
|
||||
data = dfa_client.get_per_config_profile_detail(cfg_profile)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve host details.'),
|
||||
redirect=self.get_redirect_url())
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse_lazy('horizon:cisco:dfa:index')
|
||||
|
||||
|
||||
class DetailView(tables.MultiTableView):
|
||||
table_classes = (dfatables.TopologyTable, )
|
||||
template_name = 'cisco/dfa/detail.html'
|
||||
page_title = "{{ agent.host }}"
|
||||
|
||||
def get_topology_data(self):
|
||||
try:
|
||||
topology = []
|
||||
agent = self._get_data()
|
||||
cfg = json.loads(agent.get('config'))
|
||||
topo = cfg.get('topo')
|
||||
for key in topo.keys():
|
||||
intf = topo.get(key)
|
||||
if not intf.get('remote_evb_cfgd'):
|
||||
continue
|
||||
topology.append(
|
||||
dict(
|
||||
interface=cfg.get('uplink'),
|
||||
remote_port=intf.get('remote_port'),
|
||||
bond_intf=intf.get('bond_intf'),
|
||||
remote_port_mac=intf.get('remote_port_id_mac'),
|
||||
remote_evb_cfgd=intf.get('remote_evb_cfgd'),
|
||||
remote_system_desc=intf.get('remote_system_desc'),
|
||||
remote_chassis_mac=intf.get('remote_chassis_id_mac'),
|
||||
remote_mgmt_addr=intf.get('remote_mgmt_addr'),
|
||||
remote_system_name=intf.get('remote_system_name'),
|
||||
remote_evb_mode=intf.get('remote_evb_mode')))
|
||||
except Exception:
|
||||
topology = []
|
||||
msg = _('Neighborship is not established for this server')
|
||||
exceptions.handle(self.request, msg)
|
||||
return topology
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
agent = self._get_data()
|
||||
context["agent"] = agent
|
||||
cfg = json.loads(agent.get('config'))
|
||||
context["uplink"] = cfg.get('uplink')
|
||||
context["memb_ports"] = cfg.get('memb_ports')
|
||||
context["veth_intf"] = cfg.get('veth_intf')
|
||||
context["url"] = self.get_redirect_url()
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
host_name = self.kwargs['host']
|
||||
NFEhost = dict(host=host_name)
|
||||
agent = (dfa_client.get_agent_details_per_host(NFEhost))[0]
|
||||
agent["heartbeat"] = agent.get('heartbeat').replace('T', ' ')[:-7]
|
||||
agent["created"] = agent.get('created').replace('T', ' ')[:-7]
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve host details.'),
|
||||
redirect=self.get_redirect_url())
|
||||
return agent
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
return reverse_lazy('horizon:cisco:dfa:index')
|
|
@ -43,10 +43,7 @@ class DFAConfigProfileAction(workflows.Action):
|
|||
def _get_cfg_profiles(self, request):
|
||||
profiles = []
|
||||
try:
|
||||
dfaclient = dfa_client.DFAClient()
|
||||
if not bool(dfaclient.__dict__):
|
||||
return profiles
|
||||
cfgplist = dfaclient.get_config_profiles_detail()
|
||||
cfgplist = dfa_client.get_config_profiles_detail()
|
||||
profiles = [q for p in cfgplist
|
||||
if (p.get('profileSubType') == 'network:universal')
|
||||
for q in [p.get('profileName')]]
|
||||
|
@ -89,12 +86,11 @@ class DFACreateNetwork(upstream_networks_workflows.CreateNetwork):
|
|||
if not network:
|
||||
return False
|
||||
if data['cfg_profile']:
|
||||
dfaclient = dfa_client.DFAClient()
|
||||
associate_data = {'id': network['id'],
|
||||
'cfgp': data['cfg_profile'],
|
||||
'name': network['name'],
|
||||
'tenant_id': request.user.project_id}
|
||||
dfaclient.associate_profile_with_network(associate_data)
|
||||
dfa_client.associate_profile_with_network(associate_data)
|
||||
# If we do not need to create a subnet, return here.
|
||||
if not data['with_subnet']:
|
||||
return True
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
PANEL = 'dfa'
|
||||
PANEL_GROUP = 'default'
|
||||
PANEL_DASHBOARD = 'cisco'
|
||||
|
||||
ADD_PANEL = 'horizon_cisco_ui.cisco.dfa.panel.DFA'
|
Loading…
Reference in New Issue