remove unnecessary neutron files under neutron/plugins
Change-Id: Ia27aa8af3701b740ea6750560f96fbf9ec704465
This commit is contained in:
parent
ff34dae544
commit
34b7393388
|
@ -1,14 +0,0 @@
|
||||||
# Neuron REST Proxy Plug-in for Big Switch and FloodLight Controllers
|
|
||||||
|
|
||||||
This module provides a generic neutron plugin 'NeutronRestProxy' that
|
|
||||||
translates neutron function calls to authenticated REST requests (JSON supported)
|
|
||||||
to a set of redundant external network controllers.
|
|
||||||
|
|
||||||
It also keeps a local persistent store of neutron state that has been
|
|
||||||
setup using that API.
|
|
||||||
|
|
||||||
Currently the FloodLight Openflow Controller or the Big Switch Networks Controller
|
|
||||||
can be configured as external network controllers for this plugin.
|
|
||||||
|
|
||||||
For more details on this plugin, please refer to the following link:
|
|
||||||
http://www.openflowhub.org/display/floodlightcontroller/Neutron+REST+Proxy+Plugin
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
|
@ -1,181 +0,0 @@
|
||||||
# Copyright 2014 Big Switch Networks, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Copyright 2011 VMware, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
# @author: Kevin Benton, kevin.benton@bigswitch.com
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
eventlet.monkey_patch()
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.agent.linux import ovs_lib
|
|
||||||
from neutron.agent.linux import utils
|
|
||||||
from neutron.agent import rpc as agent_rpc
|
|
||||||
from neutron.agent import securitygroups_rpc as sg_rpc
|
|
||||||
from neutron.common import config
|
|
||||||
from neutron.common import rpc_compat
|
|
||||||
from neutron.common import topics
|
|
||||||
from neutron import context as q_context
|
|
||||||
from neutron.extensions import securitygroup as ext_sg
|
|
||||||
from neutron.openstack.common import excutils
|
|
||||||
from neutron.openstack.common import log
|
|
||||||
from neutron.plugins.bigswitch import config as pl_config
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class IVSBridge(ovs_lib.OVSBridge):
|
|
||||||
'''
|
|
||||||
This class does not provide parity with OVS using IVS.
|
|
||||||
It's only the bare minimum necessary to use IVS with this agent.
|
|
||||||
'''
|
|
||||||
def run_vsctl(self, args, check_error=False):
|
|
||||||
full_args = ["ivs-ctl"] + args
|
|
||||||
try:
|
|
||||||
return utils.execute(full_args, root_helper=self.root_helper)
|
|
||||||
except Exception as e:
|
|
||||||
with excutils.save_and_reraise_exception() as ctxt:
|
|
||||||
LOG.error(_("Unable to execute %(cmd)s. "
|
|
||||||
"Exception: %(exception)s"),
|
|
||||||
{'cmd': full_args, 'exception': e})
|
|
||||||
if not check_error:
|
|
||||||
ctxt.reraise = False
|
|
||||||
|
|
||||||
def get_vif_port_set(self):
|
|
||||||
port_names = self.get_port_name_list()
|
|
||||||
edge_ports = set(port_names)
|
|
||||||
return edge_ports
|
|
||||||
|
|
||||||
def get_vif_port_by_id(self, port_id):
|
|
||||||
# IVS in nova uses hybrid method with last 14 chars of UUID
|
|
||||||
name = 'qvo%s' % port_id[:14]
|
|
||||||
if name in self.get_vif_port_set():
|
|
||||||
return name
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class PluginApi(agent_rpc.PluginApi,
|
|
||||||
sg_rpc.SecurityGroupServerRpcApiMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityGroupAgent(sg_rpc.SecurityGroupAgentRpcMixin):
|
|
||||||
def __init__(self, context, plugin_rpc, root_helper):
|
|
||||||
self.context = context
|
|
||||||
self.plugin_rpc = plugin_rpc
|
|
||||||
self.root_helper = root_helper
|
|
||||||
self.init_firewall()
|
|
||||||
|
|
||||||
|
|
||||||
class RestProxyAgent(rpc_compat.RpcCallback,
|
|
||||||
sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
|
||||||
|
|
||||||
RPC_API_VERSION = '1.1'
|
|
||||||
|
|
||||||
def __init__(self, integ_br, polling_interval, root_helper, vs='ovs'):
|
|
||||||
super(RestProxyAgent, self).__init__()
|
|
||||||
self.polling_interval = polling_interval
|
|
||||||
self._setup_rpc()
|
|
||||||
self.sg_agent = SecurityGroupAgent(self.context,
|
|
||||||
self.plugin_rpc,
|
|
||||||
root_helper)
|
|
||||||
if vs == 'ivs':
|
|
||||||
self.int_br = IVSBridge(integ_br, root_helper)
|
|
||||||
else:
|
|
||||||
self.int_br = ovs_lib.OVSBridge(integ_br, root_helper)
|
|
||||||
|
|
||||||
def _setup_rpc(self):
|
|
||||||
self.topic = topics.AGENT
|
|
||||||
self.plugin_rpc = PluginApi(topics.PLUGIN)
|
|
||||||
self.context = q_context.get_admin_context_without_session()
|
|
||||||
self.endpoints = [self]
|
|
||||||
consumers = [[topics.PORT, topics.UPDATE],
|
|
||||||
[topics.SECURITY_GROUP, topics.UPDATE]]
|
|
||||||
self.connection = agent_rpc.create_consumers(self.endpoints,
|
|
||||||
self.topic,
|
|
||||||
consumers)
|
|
||||||
|
|
||||||
def port_update(self, context, **kwargs):
|
|
||||||
LOG.debug(_("Port update received"))
|
|
||||||
port = kwargs.get('port')
|
|
||||||
vif_port = self.int_br.get_vif_port_by_id(port['id'])
|
|
||||||
if not vif_port:
|
|
||||||
LOG.debug(_("Port %s is not present on this host."), port['id'])
|
|
||||||
return
|
|
||||||
|
|
||||||
LOG.debug(_("Port %s found. Refreshing firewall."), port['id'])
|
|
||||||
if ext_sg.SECURITYGROUPS in port:
|
|
||||||
self.sg_agent.refresh_firewall()
|
|
||||||
|
|
||||||
def _update_ports(self, registered_ports):
|
|
||||||
ports = self.int_br.get_vif_port_set()
|
|
||||||
if ports == registered_ports:
|
|
||||||
return
|
|
||||||
added = ports - registered_ports
|
|
||||||
removed = registered_ports - ports
|
|
||||||
return {'current': ports,
|
|
||||||
'added': added,
|
|
||||||
'removed': removed}
|
|
||||||
|
|
||||||
def _process_devices_filter(self, port_info):
|
|
||||||
if 'added' in port_info:
|
|
||||||
self.sg_agent.prepare_devices_filter(port_info['added'])
|
|
||||||
if 'removed' in port_info:
|
|
||||||
self.sg_agent.remove_devices_filter(port_info['removed'])
|
|
||||||
|
|
||||||
def daemon_loop(self):
|
|
||||||
ports = set()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
start = time.time()
|
|
||||||
try:
|
|
||||||
port_info = self._update_ports(ports)
|
|
||||||
if port_info:
|
|
||||||
LOG.debug(_("Agent loop has new device"))
|
|
||||||
self._process_devices_filter(port_info)
|
|
||||||
ports = port_info['current']
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_("Error in agent event loop"))
|
|
||||||
|
|
||||||
elapsed = max(time.time() - start, 0)
|
|
||||||
if (elapsed < self.polling_interval):
|
|
||||||
time.sleep(self.polling_interval - elapsed)
|
|
||||||
else:
|
|
||||||
LOG.debug(_("Loop iteration exceeded interval "
|
|
||||||
"(%(polling_interval)s vs. %(elapsed)s)!"),
|
|
||||||
{'polling_interval': self.polling_interval,
|
|
||||||
'elapsed': elapsed})
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
config.init(sys.argv[1:])
|
|
||||||
config.setup_logging(cfg.CONF)
|
|
||||||
pl_config.register_config()
|
|
||||||
|
|
||||||
integ_br = cfg.CONF.RESTPROXYAGENT.integration_bridge
|
|
||||||
polling_interval = cfg.CONF.RESTPROXYAGENT.polling_interval
|
|
||||||
root_helper = cfg.CONF.AGENT.root_helper
|
|
||||||
bsnagent = RestProxyAgent(integ_br, polling_interval, root_helper,
|
|
||||||
cfg.CONF.RESTPROXYAGENT.virtual_switch_type)
|
|
||||||
bsnagent.daemon_loop()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,123 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
# Copyright 2014 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Mandeep Dhami, Big Switch Networks, Inc.
|
|
||||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
|
||||||
# @author: Kevin Benton, Big Switch Networks, Inc.
|
|
||||||
|
|
||||||
"""
|
|
||||||
This module manages configuration options
|
|
||||||
"""
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.agent.common import config as agconfig
|
|
||||||
from neutron.common import utils
|
|
||||||
from neutron.extensions import portbindings
|
|
||||||
|
|
||||||
restproxy_opts = [
|
|
||||||
cfg.ListOpt('servers', default=['localhost:8800'],
|
|
||||||
help=_("A comma separated list of Big Switch or Floodlight "
|
|
||||||
"servers and port numbers. The plugin proxies the "
|
|
||||||
"requests to the Big Switch/Floodlight server, "
|
|
||||||
"which performs the networking configuration. Only one"
|
|
||||||
"server is needed per deployment, but you may wish to"
|
|
||||||
"deploy multiple servers to support failover.")),
|
|
||||||
cfg.StrOpt('server_auth', secret=True,
|
|
||||||
help=_("The username and password for authenticating against "
|
|
||||||
" the Big Switch or Floodlight controller.")),
|
|
||||||
cfg.BoolOpt('server_ssl', default=True,
|
|
||||||
help=_("If True, Use SSL when connecting to the Big Switch or "
|
|
||||||
"Floodlight controller.")),
|
|
||||||
cfg.BoolOpt('ssl_sticky', default=True,
|
|
||||||
help=_("Trust and store the first certificate received for "
|
|
||||||
"each controller address and use it to validate future "
|
|
||||||
"connections to that address.")),
|
|
||||||
cfg.BoolOpt('no_ssl_validation', default=False,
|
|
||||||
help=_("Disables SSL certificate validation for controllers")),
|
|
||||||
cfg.BoolOpt('cache_connections', default=True,
|
|
||||||
help=_("Re-use HTTP/HTTPS connections to the controller.")),
|
|
||||||
cfg.StrOpt('ssl_cert_directory',
|
|
||||||
default='/etc/neutron/plugins/bigswitch/ssl',
|
|
||||||
help=_("Directory containing ca_certs and host_certs "
|
|
||||||
"certificate directories.")),
|
|
||||||
cfg.BoolOpt('sync_data', default=False,
|
|
||||||
help=_("Sync data on connect")),
|
|
||||||
cfg.BoolOpt('auto_sync_on_failure', default=True,
|
|
||||||
help=_("If neutron fails to create a resource because "
|
|
||||||
"the backend controller doesn't know of a dependency, "
|
|
||||||
"the plugin automatically triggers a full data "
|
|
||||||
"synchronization to the controller.")),
|
|
||||||
cfg.IntOpt('consistency_interval', default=60,
|
|
||||||
help=_("Time between verifications that the backend controller "
|
|
||||||
"database is consistent with Neutron. (0 to disable)")),
|
|
||||||
cfg.IntOpt('server_timeout', default=10,
|
|
||||||
help=_("Maximum number of seconds to wait for proxy request "
|
|
||||||
"to connect and complete.")),
|
|
||||||
cfg.IntOpt('thread_pool_size', default=4,
|
|
||||||
help=_("Maximum number of threads to spawn to handle large "
|
|
||||||
"volumes of port creations.")),
|
|
||||||
cfg.StrOpt('neutron_id', default='neutron-' + utils.get_hostname(),
|
|
||||||
deprecated_name='quantum_id',
|
|
||||||
help=_("User defined identifier for this Neutron deployment")),
|
|
||||||
cfg.BoolOpt('add_meta_server_route', default=True,
|
|
||||||
help=_("Flag to decide if a route to the metadata server "
|
|
||||||
"should be injected into the VM")),
|
|
||||||
]
|
|
||||||
router_opts = [
|
|
||||||
cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'],
|
|
||||||
help=_("The default router rules installed in new tenant "
|
|
||||||
"routers. Repeat the config option for each rule. "
|
|
||||||
"Format is <tenant>:<source>:<destination>:<action>"
|
|
||||||
" Use an * to specify default for all tenants.")),
|
|
||||||
cfg.IntOpt('max_router_rules', default=200,
|
|
||||||
help=_("Maximum number of router rules")),
|
|
||||||
]
|
|
||||||
nova_opts = [
|
|
||||||
cfg.StrOpt('vif_type', default='ovs',
|
|
||||||
help=_("Virtual interface type to configure on "
|
|
||||||
"Nova compute nodes")),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Each VIF Type can have a list of nova host IDs that are fixed to that type
|
|
||||||
for i in portbindings.VIF_TYPES:
|
|
||||||
opt = cfg.ListOpt('node_override_vif_' + i, default=[],
|
|
||||||
help=_("Nova compute nodes to manually set VIF "
|
|
||||||
"type to %s") % i)
|
|
||||||
nova_opts.append(opt)
|
|
||||||
|
|
||||||
# Add the vif types for reference later
|
|
||||||
nova_opts.append(cfg.ListOpt('vif_types',
|
|
||||||
default=portbindings.VIF_TYPES,
|
|
||||||
help=_('List of allowed vif_type values.')))
|
|
||||||
|
|
||||||
agent_opts = [
|
|
||||||
cfg.StrOpt('integration_bridge', default='br-int',
|
|
||||||
help=_('Name of integration bridge on compute '
|
|
||||||
'nodes used for security group insertion.')),
|
|
||||||
cfg.IntOpt('polling_interval', default=5,
|
|
||||||
help=_('Seconds between agent checks for port changes')),
|
|
||||||
cfg.StrOpt('virtual_switch_type', default='ovs',
|
|
||||||
help=_('Virtual switch type.'))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_config():
|
|
||||||
cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
|
|
||||||
cfg.CONF.register_opts(router_opts, "ROUTER")
|
|
||||||
cfg.CONF.register_opts(nova_opts, "NOVA")
|
|
||||||
cfg.CONF.register_opts(agent_opts, "RESTPROXYAGENT")
|
|
||||||
agconfig.register_root_helper(cfg.CONF)
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Kevin Benton, Big Switch Networks, Inc.
|
|
|
@ -1,56 +0,0 @@
|
||||||
# Copyright 2014, Big Switch Networks
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from neutron.db import api as db
|
|
||||||
from neutron.db import model_base
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
'''
|
|
||||||
A simple table to store the latest consistency hash
|
|
||||||
received from a server in case neutron gets restarted.
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class ConsistencyHash(model_base.BASEV2):
|
|
||||||
'''
|
|
||||||
For now we only support one global state so the
|
|
||||||
hash_id will always be '1'
|
|
||||||
'''
|
|
||||||
__tablename__ = 'consistencyhashes'
|
|
||||||
hash_id = sa.Column(sa.String(255),
|
|
||||||
primary_key=True)
|
|
||||||
hash = sa.Column(sa.String(255), nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
def get_consistency_hash(hash_id='1'):
|
|
||||||
session = db.get_session()
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
query = session.query(ConsistencyHash)
|
|
||||||
res = query.filter_by(hash_id=hash_id).first()
|
|
||||||
if not res:
|
|
||||||
return False
|
|
||||||
return res.hash
|
|
||||||
|
|
||||||
|
|
||||||
def put_consistency_hash(hash, hash_id='1'):
|
|
||||||
session = db.get_session()
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
conhash = ConsistencyHash(hash_id=hash_id, hash=hash)
|
|
||||||
session.merge(conhash)
|
|
||||||
LOG.debug(_("Consistency hash for group %(hash_id)s updated "
|
|
||||||
"to %(hash)s"), {'hash_id': hash_id, 'hash': hash})
|
|
|
@ -1,53 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013, Big Switch Networks
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_hostid(context, port_id):
|
|
||||||
# REVISIT(kevinbenton): this is a workaround to avoid portbindings_db
|
|
||||||
# relational table generation until one of the functions is called.
|
|
||||||
from neutron.db import portbindings_db
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
query = context.session.query(portbindings_db.PortBindingPort)
|
|
||||||
res = query.filter_by(port_id=port_id).first()
|
|
||||||
if not res:
|
|
||||||
return False
|
|
||||||
return res.host
|
|
||||||
|
|
||||||
|
|
||||||
def put_port_hostid(context, port_id, host):
|
|
||||||
# REVISIT(kevinbenton): this is a workaround to avoid portbindings_db
|
|
||||||
# relational table generation until one of the functions is called.
|
|
||||||
from neutron.db import portbindings_db
|
|
||||||
if not attributes.is_attr_set(host):
|
|
||||||
LOG.warning(_("No host_id in port request to track port location."))
|
|
||||||
return
|
|
||||||
if port_id == '':
|
|
||||||
LOG.warning(_("Received an empty port ID for host_id '%s'"), host)
|
|
||||||
return
|
|
||||||
if host == '':
|
|
||||||
LOG.debug(_("Received an empty host_id for port '%s'"), port_id)
|
|
||||||
return
|
|
||||||
LOG.debug(_("Logging port %(port)s on host_id %(host)s"),
|
|
||||||
{'port': port_id, 'host': host})
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
location = portbindings_db.PortBindingPort(port_id=port_id, host=host)
|
|
||||||
context.session.merge(location)
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Kevin Benton, Big Switch Networks, Inc.
|
|
|
@ -1,144 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Kevin Benton, Big Switch Networks, Inc.
|
|
||||||
|
|
||||||
from neutron.api.v2 import attributes as attr
|
|
||||||
from neutron.common import exceptions as qexception
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Router Rules Exceptions
|
|
||||||
class InvalidRouterRules(qexception.InvalidInput):
|
|
||||||
message = _("Invalid format for router rules: %(rule)s, %(reason)s")
|
|
||||||
|
|
||||||
|
|
||||||
class RulesExhausted(qexception.BadRequest):
|
|
||||||
message = _("Unable to complete rules update for %(router_id)s. "
|
|
||||||
"The number of rules exceeds the maximum %(quota)s.")
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_valid_router_rules(data):
|
|
||||||
"""
|
|
||||||
Validates and converts router rules to the appropriate data structure
|
|
||||||
Example argument = [{'source': 'any', 'destination': 'any',
|
|
||||||
'action':'deny'},
|
|
||||||
{'source': '1.1.1.1/32', 'destination': 'external',
|
|
||||||
'action':'permit',
|
|
||||||
'nexthops': ['1.1.1.254', '1.1.1.253']}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
V4ANY = '0.0.0.0/0'
|
|
||||||
CIDRALL = ['any', 'external']
|
|
||||||
if not isinstance(data, list):
|
|
||||||
emsg = _("Invalid data format for router rule: '%s'") % data
|
|
||||||
LOG.debug(emsg)
|
|
||||||
raise qexception.InvalidInput(error_message=emsg)
|
|
||||||
_validate_uniquerules(data)
|
|
||||||
rules = []
|
|
||||||
expected_keys = ['source', 'destination', 'action']
|
|
||||||
for rule in data:
|
|
||||||
rule['nexthops'] = rule.get('nexthops', [])
|
|
||||||
if not isinstance(rule['nexthops'], list):
|
|
||||||
rule['nexthops'] = rule['nexthops'].split('+')
|
|
||||||
|
|
||||||
src = V4ANY if rule['source'] in CIDRALL else rule['source']
|
|
||||||
dst = V4ANY if rule['destination'] in CIDRALL else rule['destination']
|
|
||||||
|
|
||||||
errors = [attr._verify_dict_keys(expected_keys, rule, False),
|
|
||||||
attr._validate_subnet(dst),
|
|
||||||
attr._validate_subnet(src),
|
|
||||||
_validate_nexthops(rule['nexthops']),
|
|
||||||
_validate_action(rule['action'])]
|
|
||||||
errors = [m for m in errors if m]
|
|
||||||
if errors:
|
|
||||||
LOG.debug(errors)
|
|
||||||
raise qexception.InvalidInput(error_message=errors)
|
|
||||||
rules.append(rule)
|
|
||||||
return rules
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_nexthops(nexthops):
|
|
||||||
seen = []
|
|
||||||
for ip in nexthops:
|
|
||||||
msg = attr._validate_ip_address(ip)
|
|
||||||
if ip in seen:
|
|
||||||
msg = _("Duplicate nexthop in rule '%s'") % ip
|
|
||||||
seen.append(ip)
|
|
||||||
if msg:
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_action(action):
|
|
||||||
if action not in ['permit', 'deny']:
|
|
||||||
return _("Action must be either permit or deny."
|
|
||||||
" '%s' was provided") % action
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_uniquerules(rules):
|
|
||||||
pairs = []
|
|
||||||
for r in rules:
|
|
||||||
if 'source' not in r or 'destination' not in r:
|
|
||||||
continue
|
|
||||||
pairs.append((r['source'], r['destination']))
|
|
||||||
|
|
||||||
if len(set(pairs)) != len(pairs):
|
|
||||||
error = _("Duplicate router rules (src,dst) found '%s'") % pairs
|
|
||||||
LOG.debug(error)
|
|
||||||
raise qexception.InvalidInput(error_message=error)
|
|
||||||
|
|
||||||
|
|
||||||
class Routerrule(object):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "Neutron Router Rule"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return "router_rules"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Router rule configuration for L3 router"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://docs.openstack.org/ext/neutron/routerrules/api/v1.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2013-05-23T10:00:00-00:00"
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return EXTENDED_ATTRIBUTES_2_0
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Attribute Map
|
|
||||||
EXTENDED_ATTRIBUTES_2_0 = {
|
|
||||||
'routers': {
|
|
||||||
'router_rules': {'allow_post': False, 'allow_put': True,
|
|
||||||
'convert_to': convert_to_valid_router_rules,
|
|
||||||
'is_visible': True,
|
|
||||||
'default': attr.ATTR_NOT_SPECIFIED},
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,148 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013, Big Switch Networks
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy import orm
|
|
||||||
|
|
||||||
from neutron.db import l3_db
|
|
||||||
from neutron.db import model_base
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.bigswitch.extensions import routerrule
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RouterRule(model_base.BASEV2):
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
source = sa.Column(sa.String(64), nullable=False)
|
|
||||||
destination = sa.Column(sa.String(64), nullable=False)
|
|
||||||
nexthops = orm.relationship('NextHop', cascade='all,delete')
|
|
||||||
action = sa.Column(sa.String(10), nullable=False)
|
|
||||||
router_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('routers.id',
|
|
||||||
ondelete="CASCADE"))
|
|
||||||
|
|
||||||
|
|
||||||
class NextHop(model_base.BASEV2):
|
|
||||||
rule_id = sa.Column(sa.Integer,
|
|
||||||
sa.ForeignKey('routerrules.id',
|
|
||||||
ondelete="CASCADE"),
|
|
||||||
primary_key=True)
|
|
||||||
nexthop = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
class RouterRule_db_mixin(l3_db.L3_NAT_db_mixin):
|
|
||||||
""" Mixin class to support route rule configuration on a router"""
|
|
||||||
def update_router(self, context, id, router):
|
|
||||||
r = router['router']
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
router_db = self._get_router(context, id)
|
|
||||||
if 'router_rules' in r:
|
|
||||||
self._update_router_rules(context,
|
|
||||||
router_db,
|
|
||||||
r['router_rules'])
|
|
||||||
updated = super(RouterRule_db_mixin, self).update_router(
|
|
||||||
context, id, router)
|
|
||||||
updated['router_rules'] = self._get_router_rules_by_router_id(
|
|
||||||
context, id)
|
|
||||||
|
|
||||||
return updated
|
|
||||||
|
|
||||||
def create_router(self, context, router):
|
|
||||||
r = router['router']
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
router_db = super(RouterRule_db_mixin, self).create_router(
|
|
||||||
context, router)
|
|
||||||
if 'router_rules' in r:
|
|
||||||
self._update_router_rules(context,
|
|
||||||
router_db,
|
|
||||||
r['router_rules'])
|
|
||||||
else:
|
|
||||||
LOG.debug(_('No rules in router'))
|
|
||||||
router_db['router_rules'] = self._get_router_rules_by_router_id(
|
|
||||||
context, router_db['id'])
|
|
||||||
|
|
||||||
return router_db
|
|
||||||
|
|
||||||
def _update_router_rules(self, context, router, rules):
|
|
||||||
if len(rules) > cfg.CONF.ROUTER.max_router_rules:
|
|
||||||
raise routerrule.RulesExhausted(
|
|
||||||
router_id=router['id'],
|
|
||||||
quota=cfg.CONF.ROUTER.max_router_rules)
|
|
||||||
del_context = context.session.query(RouterRule)
|
|
||||||
del_context.filter_by(router_id=router['id']).delete()
|
|
||||||
context.session.expunge_all()
|
|
||||||
LOG.debug(_('Updating router rules to %s'), rules)
|
|
||||||
for rule in rules:
|
|
||||||
router_rule = RouterRule(
|
|
||||||
router_id=router['id'],
|
|
||||||
destination=rule['destination'],
|
|
||||||
source=rule['source'],
|
|
||||||
action=rule['action'])
|
|
||||||
router_rule.nexthops = [NextHop(nexthop=hop)
|
|
||||||
for hop in rule['nexthops']]
|
|
||||||
context.session.add(router_rule)
|
|
||||||
context.session.flush()
|
|
||||||
|
|
||||||
def _make_router_rule_list(self, router_rules):
|
|
||||||
ruleslist = []
|
|
||||||
for rule in router_rules:
|
|
||||||
hops = [hop['nexthop'] for hop in rule['nexthops']]
|
|
||||||
ruleslist.append({'id': rule['id'],
|
|
||||||
'destination': rule['destination'],
|
|
||||||
'source': rule['source'],
|
|
||||||
'action': rule['action'],
|
|
||||||
'nexthops': hops})
|
|
||||||
return ruleslist
|
|
||||||
|
|
||||||
def _get_router_rules_by_router_id(self, context, id):
|
|
||||||
query = context.session.query(RouterRule)
|
|
||||||
router_rules = query.filter_by(router_id=id).all()
|
|
||||||
return self._make_router_rule_list(router_rules)
|
|
||||||
|
|
||||||
def get_router(self, context, id, fields=None):
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
router = super(RouterRule_db_mixin, self).get_router(
|
|
||||||
context, id, fields)
|
|
||||||
router['router_rules'] = self._get_router_rules_by_router_id(
|
|
||||||
context, id)
|
|
||||||
return router
|
|
||||||
|
|
||||||
def get_routers(self, context, filters=None, fields=None,
|
|
||||||
sorts=None, limit=None, marker=None,
|
|
||||||
page_reverse=False):
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
routers = super(RouterRule_db_mixin, self).get_routers(
|
|
||||||
context, filters, fields, sorts=sorts, limit=limit,
|
|
||||||
marker=marker, page_reverse=page_reverse)
|
|
||||||
for router in routers:
|
|
||||||
router['router_rules'] = self._get_router_rules_by_router_id(
|
|
||||||
context, router['id'])
|
|
||||||
return routers
|
|
||||||
|
|
||||||
def get_sync_data(self, context, router_ids=None, active=None):
|
|
||||||
"""Query routers and their related floating_ips, interfaces."""
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
routers = super(RouterRule_db_mixin,
|
|
||||||
self).get_sync_data(context, router_ids,
|
|
||||||
active=active)
|
|
||||||
for router in routers:
|
|
||||||
router['router_rules'] = self._get_router_rules_by_router_id(
|
|
||||||
context, router['id'])
|
|
||||||
return routers
|
|
|
@ -1,595 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
# Copyright 2014 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Mandeep Dhami, Big Switch Networks, Inc.
|
|
||||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
|
||||||
# @author: Kevin Benton, Big Switch Networks, Inc.
|
|
||||||
|
|
||||||
"""
|
|
||||||
This module manages the HTTP and HTTPS connections to the backend controllers.
|
|
||||||
|
|
||||||
The main class it provides for external use is ServerPool which manages a set
|
|
||||||
of ServerProxy objects that correspond to individual backend controllers.
|
|
||||||
|
|
||||||
The following functionality is handled by this module:
|
|
||||||
- Translation of rest_* function calls to HTTP/HTTPS calls to the controllers
|
|
||||||
- Automatic failover between controllers
|
|
||||||
- SSL Certificate enforcement
|
|
||||||
- HTTP Authentication
|
|
||||||
|
|
||||||
"""
|
|
||||||
import base64
|
|
||||||
import httplib
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.common import exceptions
|
|
||||||
from neutron.common import utils
|
|
||||||
from neutron.openstack.common import excutils
|
|
||||||
from neutron.openstack.common import jsonutils as json
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.bigswitch.db import consistency_db as cdb
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# The following are used to invoke the API on the external controller
|
|
||||||
CAPABILITIES_PATH = "/capabilities"
|
|
||||||
NET_RESOURCE_PATH = "/tenants/%s/networks"
|
|
||||||
PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports"
|
|
||||||
ROUTER_RESOURCE_PATH = "/tenants/%s/routers"
|
|
||||||
ROUTER_INTF_OP_PATH = "/tenants/%s/routers/%s/interfaces"
|
|
||||||
NETWORKS_PATH = "/tenants/%s/networks/%s"
|
|
||||||
FLOATINGIPS_PATH = "/tenants/%s/floatingips/%s"
|
|
||||||
PORTS_PATH = "/tenants/%s/networks/%s/ports/%s"
|
|
||||||
ATTACHMENT_PATH = "/tenants/%s/networks/%s/ports/%s/attachment"
|
|
||||||
ROUTERS_PATH = "/tenants/%s/routers/%s"
|
|
||||||
ROUTER_INTF_PATH = "/tenants/%s/routers/%s/interfaces/%s"
|
|
||||||
TOPOLOGY_PATH = "/topology"
|
|
||||||
HEALTH_PATH = "/health"
|
|
||||||
SUCCESS_CODES = range(200, 207)
|
|
||||||
FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
|
|
||||||
504, 505]
|
|
||||||
BASE_URI = '/networkService/v1.1'
|
|
||||||
ORCHESTRATION_SERVICE_ID = 'Neutron v2.0'
|
|
||||||
HASH_MATCH_HEADER = 'X-BSN-BVS-HASH-MATCH'
|
|
||||||
# error messages
|
|
||||||
NXNETWORK = 'NXVNS'
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteRestError(exceptions.NeutronException):
|
|
||||||
message = _("Error in REST call to remote network "
|
|
||||||
"controller: %(reason)s")
|
|
||||||
status = None
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.status = kwargs.pop('status', None)
|
|
||||||
self.reason = kwargs.get('reason')
|
|
||||||
super(RemoteRestError, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ServerProxy(object):
|
|
||||||
"""REST server proxy to a network controller."""
|
|
||||||
|
|
||||||
def __init__(self, server, port, ssl, auth, neutron_id, timeout,
|
|
||||||
base_uri, name, mypool, combined_cert):
|
|
||||||
self.server = server
|
|
||||||
self.port = port
|
|
||||||
self.ssl = ssl
|
|
||||||
self.base_uri = base_uri
|
|
||||||
self.timeout = timeout
|
|
||||||
self.name = name
|
|
||||||
self.success_codes = SUCCESS_CODES
|
|
||||||
self.auth = None
|
|
||||||
self.neutron_id = neutron_id
|
|
||||||
self.failed = False
|
|
||||||
self.capabilities = []
|
|
||||||
# enable server to reference parent pool
|
|
||||||
self.mypool = mypool
|
|
||||||
# cache connection here to avoid a SSL handshake for every connection
|
|
||||||
self.currentconn = None
|
|
||||||
if auth:
|
|
||||||
self.auth = 'Basic ' + base64.encodestring(auth).strip()
|
|
||||||
self.combined_cert = combined_cert
|
|
||||||
|
|
||||||
def get_capabilities(self):
|
|
||||||
try:
|
|
||||||
body = self.rest_call('GET', CAPABILITIES_PATH)[2]
|
|
||||||
self.capabilities = json.loads(body)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_("Couldn't retrieve capabilities. "
|
|
||||||
"Newer API calls won't be supported."))
|
|
||||||
LOG.info(_("The following capabilities were received "
|
|
||||||
"for %(server)s: %(cap)s"), {'server': self.server,
|
|
||||||
'cap': self.capabilities})
|
|
||||||
return self.capabilities
|
|
||||||
|
|
||||||
def rest_call(self, action, resource, data='', headers={}, timeout=False,
|
|
||||||
reconnect=False):
|
|
||||||
uri = self.base_uri + resource
|
|
||||||
body = json.dumps(data)
|
|
||||||
if not headers:
|
|
||||||
headers = {}
|
|
||||||
headers['Content-type'] = 'application/json'
|
|
||||||
headers['Accept'] = 'application/json'
|
|
||||||
headers['NeutronProxy-Agent'] = self.name
|
|
||||||
headers['Instance-ID'] = self.neutron_id
|
|
||||||
headers['Orchestration-Service-ID'] = ORCHESTRATION_SERVICE_ID
|
|
||||||
headers[HASH_MATCH_HEADER] = self.mypool.consistency_hash or ''
|
|
||||||
if 'keep-alive' in self.capabilities:
|
|
||||||
headers['Connection'] = 'keep-alive'
|
|
||||||
else:
|
|
||||||
reconnect = True
|
|
||||||
if self.auth:
|
|
||||||
headers['Authorization'] = self.auth
|
|
||||||
|
|
||||||
LOG.debug(_("ServerProxy: server=%(server)s, port=%(port)d, "
|
|
||||||
"ssl=%(ssl)r"),
|
|
||||||
{'server': self.server, 'port': self.port, 'ssl': self.ssl})
|
|
||||||
LOG.debug(_("ServerProxy: resource=%(resource)s, data=%(data)r, "
|
|
||||||
"headers=%(headers)r, action=%(action)s"),
|
|
||||||
{'resource': resource, 'data': data, 'headers': headers,
|
|
||||||
'action': action})
|
|
||||||
|
|
||||||
# unspecified timeout is False because a timeout can be specified as
|
|
||||||
# None to indicate no timeout.
|
|
||||||
if timeout is False:
|
|
||||||
timeout = self.timeout
|
|
||||||
|
|
||||||
if timeout != self.timeout:
|
|
||||||
# need a new connection if timeout has changed
|
|
||||||
reconnect = True
|
|
||||||
|
|
||||||
if not self.currentconn or reconnect:
|
|
||||||
if self.currentconn:
|
|
||||||
self.currentconn.close()
|
|
||||||
if self.ssl:
|
|
||||||
self.currentconn = HTTPSConnectionWithValidation(
|
|
||||||
self.server, self.port, timeout=timeout)
|
|
||||||
if self.currentconn is None:
|
|
||||||
LOG.error(_('ServerProxy: Could not establish HTTPS '
|
|
||||||
'connection'))
|
|
||||||
return 0, None, None, None
|
|
||||||
self.currentconn.combined_cert = self.combined_cert
|
|
||||||
else:
|
|
||||||
self.currentconn = httplib.HTTPConnection(
|
|
||||||
self.server, self.port, timeout=timeout)
|
|
||||||
if self.currentconn is None:
|
|
||||||
LOG.error(_('ServerProxy: Could not establish HTTP '
|
|
||||||
'connection'))
|
|
||||||
return 0, None, None, None
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.currentconn.request(action, uri, body, headers)
|
|
||||||
response = self.currentconn.getresponse()
|
|
||||||
newhash = response.getheader(HASH_MATCH_HEADER)
|
|
||||||
if newhash:
|
|
||||||
self._put_consistency_hash(newhash)
|
|
||||||
respstr = response.read()
|
|
||||||
respdata = respstr
|
|
||||||
if response.status in self.success_codes:
|
|
||||||
try:
|
|
||||||
respdata = json.loads(respstr)
|
|
||||||
except ValueError:
|
|
||||||
# response was not JSON, ignore the exception
|
|
||||||
pass
|
|
||||||
ret = (response.status, response.reason, respstr, respdata)
|
|
||||||
except httplib.HTTPException:
|
|
||||||
# If we were using a cached connection, try again with a new one.
|
|
||||||
with excutils.save_and_reraise_exception() as ctxt:
|
|
||||||
self.currentconn.close()
|
|
||||||
if reconnect:
|
|
||||||
# if reconnect is true, this was on a fresh connection so
|
|
||||||
# reraise since this server seems to be broken
|
|
||||||
ctxt.reraise = True
|
|
||||||
else:
|
|
||||||
# if reconnect is false, it was a cached connection so
|
|
||||||
# try one more time before re-raising
|
|
||||||
ctxt.reraise = False
|
|
||||||
return self.rest_call(action, resource, data, headers,
|
|
||||||
timeout=timeout, reconnect=True)
|
|
||||||
except (socket.timeout, socket.error) as e:
|
|
||||||
self.currentconn.close()
|
|
||||||
LOG.error(_('ServerProxy: %(action)s failure, %(e)r'),
|
|
||||||
{'action': action, 'e': e})
|
|
||||||
ret = 0, None, None, None
|
|
||||||
LOG.debug(_("ServerProxy: status=%(status)d, reason=%(reason)r, "
|
|
||||||
"ret=%(ret)s, data=%(data)r"), {'status': ret[0],
|
|
||||||
'reason': ret[1],
|
|
||||||
'ret': ret[2],
|
|
||||||
'data': ret[3]})
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _put_consistency_hash(self, newhash):
|
|
||||||
self.mypool.consistency_hash = newhash
|
|
||||||
cdb.put_consistency_hash(newhash)
|
|
||||||
|
|
||||||
|
|
||||||
class ServerPool(object):
|
|
||||||
|
|
||||||
def __init__(self, timeout=False,
|
|
||||||
base_uri=BASE_URI, name='NeutronRestProxy'):
|
|
||||||
LOG.debug(_("ServerPool: initializing"))
|
|
||||||
# 'servers' is the list of network controller REST end-points
|
|
||||||
# (used in order specified till one succeeds, and it is sticky
|
|
||||||
# till next failure). Use 'server_auth' to encode api-key
|
|
||||||
servers = cfg.CONF.RESTPROXY.servers
|
|
||||||
self.auth = cfg.CONF.RESTPROXY.server_auth
|
|
||||||
self.ssl = cfg.CONF.RESTPROXY.server_ssl
|
|
||||||
self.neutron_id = cfg.CONF.RESTPROXY.neutron_id
|
|
||||||
self.base_uri = base_uri
|
|
||||||
self.name = name
|
|
||||||
self.timeout = cfg.CONF.RESTPROXY.server_timeout
|
|
||||||
self.always_reconnect = not cfg.CONF.RESTPROXY.cache_connections
|
|
||||||
default_port = 8000
|
|
||||||
if timeout is not False:
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
# Function to use to retrieve topology for consistency syncs.
|
|
||||||
# Needs to be set by module that uses the servermanager.
|
|
||||||
self.get_topo_function = None
|
|
||||||
self.get_topo_function_args = {}
|
|
||||||
|
|
||||||
# Hash to send to backend with request as expected previous
|
|
||||||
# state to verify consistency.
|
|
||||||
self.consistency_hash = cdb.get_consistency_hash()
|
|
||||||
|
|
||||||
if not servers:
|
|
||||||
raise cfg.Error(_('Servers not defined. Aborting server manager.'))
|
|
||||||
servers = [s if len(s.rsplit(':', 1)) == 2
|
|
||||||
else "%s:%d" % (s, default_port)
|
|
||||||
for s in servers]
|
|
||||||
if any((len(spl) != 2 or not spl[1].isdigit())
|
|
||||||
for spl in [sp.rsplit(':', 1)
|
|
||||||
for sp in servers]):
|
|
||||||
raise cfg.Error(_('Servers must be defined as <ip>:<port>. '
|
|
||||||
'Configuration was %s') % servers)
|
|
||||||
self.servers = [
|
|
||||||
self.server_proxy_for(server, int(port))
|
|
||||||
for server, port in (s.rsplit(':', 1) for s in servers)
|
|
||||||
]
|
|
||||||
eventlet.spawn(self._consistency_watchdog,
|
|
||||||
cfg.CONF.RESTPROXY.consistency_interval)
|
|
||||||
LOG.debug(_("ServerPool: initialization done"))
|
|
||||||
|
|
||||||
def get_capabilities(self):
|
|
||||||
# lookup on first try
|
|
||||||
try:
|
|
||||||
return self.capabilities
|
|
||||||
except AttributeError:
|
|
||||||
# each server should return a list of capabilities it supports
|
|
||||||
# e.g. ['floatingip']
|
|
||||||
capabilities = [set(server.get_capabilities())
|
|
||||||
for server in self.servers]
|
|
||||||
# Pool only supports what all of the servers support
|
|
||||||
self.capabilities = set.intersection(*capabilities)
|
|
||||||
return self.capabilities
|
|
||||||
|
|
||||||
def server_proxy_for(self, server, port):
|
|
||||||
combined_cert = self._get_combined_cert_for_server(server, port)
|
|
||||||
return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
|
|
||||||
self.timeout, self.base_uri, self.name, mypool=self,
|
|
||||||
combined_cert=combined_cert)
|
|
||||||
|
|
||||||
def _get_combined_cert_for_server(self, server, port):
|
|
||||||
# The ssl library requires a combined file with all trusted certs
|
|
||||||
# so we make one containing the trusted CAs and the corresponding
|
|
||||||
# host cert for this server
|
|
||||||
combined_cert = None
|
|
||||||
if self.ssl and not cfg.CONF.RESTPROXY.no_ssl_validation:
|
|
||||||
base_ssl = cfg.CONF.RESTPROXY.ssl_cert_directory
|
|
||||||
host_dir = os.path.join(base_ssl, 'host_certs')
|
|
||||||
ca_dir = os.path.join(base_ssl, 'ca_certs')
|
|
||||||
combined_dir = os.path.join(base_ssl, 'combined')
|
|
||||||
combined_cert = os.path.join(combined_dir, '%s.pem' % server)
|
|
||||||
if not os.path.exists(base_ssl):
|
|
||||||
raise cfg.Error(_('ssl_cert_directory [%s] does not exist. '
|
|
||||||
'Create it or disable ssl.') % base_ssl)
|
|
||||||
for automake in [combined_dir, ca_dir, host_dir]:
|
|
||||||
if not os.path.exists(automake):
|
|
||||||
os.makedirs(automake)
|
|
||||||
|
|
||||||
# get all CA certs
|
|
||||||
certs = self._get_ca_cert_paths(ca_dir)
|
|
||||||
|
|
||||||
# check for a host specific cert
|
|
||||||
hcert, exists = self._get_host_cert_path(host_dir, server)
|
|
||||||
if exists:
|
|
||||||
certs.append(hcert)
|
|
||||||
elif cfg.CONF.RESTPROXY.ssl_sticky:
|
|
||||||
self._fetch_and_store_cert(server, port, hcert)
|
|
||||||
certs.append(hcert)
|
|
||||||
if not certs:
|
|
||||||
raise cfg.Error(_('No certificates were found to verify '
|
|
||||||
'controller %s') % (server))
|
|
||||||
self._combine_certs_to_file(certs, combined_cert)
|
|
||||||
return combined_cert
|
|
||||||
|
|
||||||
def _combine_certs_to_file(self, certs, cfile):
|
|
||||||
'''
|
|
||||||
Concatenates the contents of each certificate in a list of
|
|
||||||
certificate paths to one combined location for use with ssl
|
|
||||||
sockets.
|
|
||||||
'''
|
|
||||||
with open(cfile, 'w') as combined:
|
|
||||||
for c in certs:
|
|
||||||
with open(c, 'r') as cert_handle:
|
|
||||||
combined.write(cert_handle.read())
|
|
||||||
|
|
||||||
def _get_host_cert_path(self, host_dir, server):
|
|
||||||
'''
|
|
||||||
returns full path and boolean indicating existence
|
|
||||||
'''
|
|
||||||
hcert = os.path.join(host_dir, '%s.pem' % server)
|
|
||||||
if os.path.exists(hcert):
|
|
||||||
return hcert, True
|
|
||||||
return hcert, False
|
|
||||||
|
|
||||||
def _get_ca_cert_paths(self, ca_dir):
|
|
||||||
certs = [os.path.join(root, name)
|
|
||||||
for name in [
|
|
||||||
name for (root, dirs, files) in os.walk(ca_dir)
|
|
||||||
for name in files
|
|
||||||
]
|
|
||||||
if name.endswith('.pem')]
|
|
||||||
return certs
|
|
||||||
|
|
||||||
def _fetch_and_store_cert(self, server, port, path):
|
|
||||||
'''
|
|
||||||
Grabs a certificate from a server and writes it to
|
|
||||||
a given path.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
cert = ssl.get_server_certificate((server, port))
|
|
||||||
except Exception as e:
|
|
||||||
raise cfg.Error(_('Could not retrieve initial '
|
|
||||||
'certificate from controller %(server)s. '
|
|
||||||
'Error details: %(error)s') %
|
|
||||||
{'server': server, 'error': str(e)})
|
|
||||||
|
|
||||||
LOG.warning(_("Storing to certificate for host %(server)s "
|
|
||||||
"at %(path)s") % {'server': server,
|
|
||||||
'path': path})
|
|
||||||
self._file_put_contents(path, cert)
|
|
||||||
|
|
||||||
return cert
|
|
||||||
|
|
||||||
def _file_put_contents(self, path, contents):
|
|
||||||
# Simple method to write to file.
|
|
||||||
# Created for easy Mocking
|
|
||||||
with open(path, 'w') as handle:
|
|
||||||
handle.write(contents)
|
|
||||||
|
|
||||||
def server_failure(self, resp, ignore_codes=[]):
|
|
||||||
"""Define failure codes as required.
|
|
||||||
|
|
||||||
Note: We assume 301-303 is a failure, and try the next server in
|
|
||||||
the server pool.
|
|
||||||
"""
|
|
||||||
return (resp[0] in FAILURE_CODES and resp[0] not in ignore_codes)
|
|
||||||
|
|
||||||
def action_success(self, resp):
|
|
||||||
"""Defining success codes as required.
|
|
||||||
|
|
||||||
Note: We assume any valid 2xx as being successful response.
|
|
||||||
"""
|
|
||||||
return resp[0] in SUCCESS_CODES
|
|
||||||
|
|
||||||
@utils.synchronized('bsn-rest-call')
|
|
||||||
def rest_call(self, action, resource, data, headers, ignore_codes,
|
|
||||||
timeout=False):
|
|
||||||
good_first = sorted(self.servers, key=lambda x: x.failed)
|
|
||||||
first_response = None
|
|
||||||
for active_server in good_first:
|
|
||||||
ret = active_server.rest_call(action, resource, data, headers,
|
|
||||||
timeout,
|
|
||||||
reconnect=self.always_reconnect)
|
|
||||||
# If inconsistent, do a full synchronization
|
|
||||||
if ret[0] == httplib.CONFLICT:
|
|
||||||
if not self.get_topo_function:
|
|
||||||
raise cfg.Error(_('Server requires synchronization, '
|
|
||||||
'but no topology function was defined.'))
|
|
||||||
data = self.get_topo_function(**self.get_topo_function_args)
|
|
||||||
active_server.rest_call('PUT', TOPOLOGY_PATH, data,
|
|
||||||
timeout=None)
|
|
||||||
# Store the first response as the error to be bubbled up to the
|
|
||||||
# user since it was a good server. Subsequent servers will most
|
|
||||||
# likely be cluster slaves and won't have a useful error for the
|
|
||||||
# user (e.g. 302 redirect to master)
|
|
||||||
if not first_response:
|
|
||||||
first_response = ret
|
|
||||||
if not self.server_failure(ret, ignore_codes):
|
|
||||||
active_server.failed = False
|
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
LOG.error(_('ServerProxy: %(action)s failure for servers: '
|
|
||||||
'%(server)r Response: %(response)s'),
|
|
||||||
{'action': action,
|
|
||||||
'server': (active_server.server,
|
|
||||||
active_server.port),
|
|
||||||
'response': ret[3]})
|
|
||||||
LOG.error(_("ServerProxy: Error details: status=%(status)d, "
|
|
||||||
"reason=%(reason)r, ret=%(ret)s, data=%(data)r"),
|
|
||||||
{'status': ret[0], 'reason': ret[1], 'ret': ret[2],
|
|
||||||
'data': ret[3]})
|
|
||||||
active_server.failed = True
|
|
||||||
|
|
||||||
# All servers failed, reset server list and try again next time
|
|
||||||
LOG.error(_('ServerProxy: %(action)s failure for all servers: '
|
|
||||||
'%(server)r'),
|
|
||||||
{'action': action,
|
|
||||||
'server': tuple((s.server,
|
|
||||||
s.port) for s in self.servers)})
|
|
||||||
return first_response
|
|
||||||
|
|
||||||
def rest_action(self, action, resource, data='', errstr='%s',
|
|
||||||
ignore_codes=[], headers={}, timeout=False):
|
|
||||||
"""
|
|
||||||
Wrapper for rest_call that verifies success and raises a
|
|
||||||
RemoteRestError on failure with a provided error string
|
|
||||||
By default, 404 errors on DELETE calls are ignored because
|
|
||||||
they already do not exist on the backend.
|
|
||||||
"""
|
|
||||||
if not ignore_codes and action == 'DELETE':
|
|
||||||
ignore_codes = [404]
|
|
||||||
resp = self.rest_call(action, resource, data, headers, ignore_codes,
|
|
||||||
timeout)
|
|
||||||
if self.server_failure(resp, ignore_codes):
|
|
||||||
LOG.error(errstr, resp[2])
|
|
||||||
raise RemoteRestError(reason=resp[2], status=resp[0])
|
|
||||||
if resp[0] in ignore_codes:
|
|
||||||
LOG.warning(_("NeutronRestProxyV2: Received and ignored error "
|
|
||||||
"code %(code)s on %(action)s action to resource "
|
|
||||||
"%(resource)s"),
|
|
||||||
{'code': resp[2], 'action': action,
|
|
||||||
'resource': resource})
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def rest_create_router(self, tenant_id, router):
|
|
||||||
resource = ROUTER_RESOURCE_PATH % tenant_id
|
|
||||||
data = {"router": router}
|
|
||||||
errstr = _("Unable to create remote router: %s")
|
|
||||||
self.rest_action('POST', resource, data, errstr)
|
|
||||||
|
|
||||||
def rest_update_router(self, tenant_id, router, router_id):
|
|
||||||
resource = ROUTERS_PATH % (tenant_id, router_id)
|
|
||||||
data = {"router": router}
|
|
||||||
errstr = _("Unable to update remote router: %s")
|
|
||||||
self.rest_action('PUT', resource, data, errstr)
|
|
||||||
|
|
||||||
def rest_delete_router(self, tenant_id, router_id):
|
|
||||||
resource = ROUTERS_PATH % (tenant_id, router_id)
|
|
||||||
errstr = _("Unable to delete remote router: %s")
|
|
||||||
self.rest_action('DELETE', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def rest_add_router_interface(self, tenant_id, router_id, intf_details):
|
|
||||||
resource = ROUTER_INTF_OP_PATH % (tenant_id, router_id)
|
|
||||||
data = {"interface": intf_details}
|
|
||||||
errstr = _("Unable to add router interface: %s")
|
|
||||||
self.rest_action('POST', resource, data, errstr)
|
|
||||||
|
|
||||||
def rest_remove_router_interface(self, tenant_id, router_id, interface_id):
|
|
||||||
resource = ROUTER_INTF_PATH % (tenant_id, router_id, interface_id)
|
|
||||||
errstr = _("Unable to delete remote intf: %s")
|
|
||||||
self.rest_action('DELETE', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def rest_create_network(self, tenant_id, network):
|
|
||||||
resource = NET_RESOURCE_PATH % tenant_id
|
|
||||||
data = {"network": network}
|
|
||||||
errstr = _("Unable to create remote network: %s")
|
|
||||||
self.rest_action('POST', resource, data, errstr)
|
|
||||||
|
|
||||||
def rest_update_network(self, tenant_id, net_id, network):
|
|
||||||
resource = NETWORKS_PATH % (tenant_id, net_id)
|
|
||||||
data = {"network": network}
|
|
||||||
errstr = _("Unable to update remote network: %s")
|
|
||||||
self.rest_action('PUT', resource, data, errstr)
|
|
||||||
|
|
||||||
def rest_delete_network(self, tenant_id, net_id):
|
|
||||||
resource = NETWORKS_PATH % (tenant_id, net_id)
|
|
||||||
errstr = _("Unable to update remote network: %s")
|
|
||||||
self.rest_action('DELETE', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def rest_create_port(self, tenant_id, net_id, port):
|
|
||||||
resource = ATTACHMENT_PATH % (tenant_id, net_id, port["id"])
|
|
||||||
data = {"port": port}
|
|
||||||
device_id = port.get("device_id")
|
|
||||||
if not port["mac_address"] or not device_id:
|
|
||||||
# controller only cares about ports attached to devices
|
|
||||||
LOG.warning(_("No device MAC attached to port %s. "
|
|
||||||
"Skipping notification to controller."), port["id"])
|
|
||||||
return
|
|
||||||
data["attachment"] = {"id": device_id,
|
|
||||||
"mac": port["mac_address"]}
|
|
||||||
errstr = _("Unable to create remote port: %s")
|
|
||||||
self.rest_action('PUT', resource, data, errstr)
|
|
||||||
|
|
||||||
def rest_delete_port(self, tenant_id, network_id, port_id):
|
|
||||||
resource = ATTACHMENT_PATH % (tenant_id, network_id, port_id)
|
|
||||||
errstr = _("Unable to delete remote port: %s")
|
|
||||||
self.rest_action('DELETE', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def rest_update_port(self, tenant_id, net_id, port):
|
|
||||||
# Controller has no update operation for the port endpoint
|
|
||||||
# the create PUT method will replace
|
|
||||||
self.rest_create_port(tenant_id, net_id, port)
|
|
||||||
|
|
||||||
def rest_create_floatingip(self, tenant_id, floatingip):
|
|
||||||
resource = FLOATINGIPS_PATH % (tenant_id, floatingip['id'])
|
|
||||||
errstr = _("Unable to create floating IP: %s")
|
|
||||||
self.rest_action('PUT', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def rest_update_floatingip(self, tenant_id, floatingip, oldid):
|
|
||||||
resource = FLOATINGIPS_PATH % (tenant_id, oldid)
|
|
||||||
errstr = _("Unable to update floating IP: %s")
|
|
||||||
self.rest_action('PUT', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def rest_delete_floatingip(self, tenant_id, oldid):
|
|
||||||
resource = FLOATINGIPS_PATH % (tenant_id, oldid)
|
|
||||||
errstr = _("Unable to delete floating IP: %s")
|
|
||||||
self.rest_action('DELETE', resource, errstr=errstr)
|
|
||||||
|
|
||||||
def _consistency_watchdog(self, polling_interval=60):
|
|
||||||
if 'consistency' not in self.get_capabilities():
|
|
||||||
LOG.warning(_("Backend server(s) do not support automated "
|
|
||||||
"consitency checks."))
|
|
||||||
return
|
|
||||||
if not polling_interval:
|
|
||||||
LOG.warning(_("Consistency watchdog disabled by polling interval "
|
|
||||||
"setting of %s."), polling_interval)
|
|
||||||
return
|
|
||||||
while True:
|
|
||||||
# If consistency is supported, all we have to do is make any
|
|
||||||
# rest call and the consistency header will be added. If it
|
|
||||||
# doesn't match, the backend will return a synchronization error
|
|
||||||
# that will be handled by the rest_action.
|
|
||||||
eventlet.sleep(polling_interval)
|
|
||||||
try:
|
|
||||||
self.rest_action('GET', HEALTH_PATH)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_("Encountered an error checking controller "
|
|
||||||
"health."))
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPSConnectionWithValidation(httplib.HTTPSConnection):
|
|
||||||
|
|
||||||
# If combined_cert is None, the connection will continue without
|
|
||||||
# any certificate validation.
|
|
||||||
combined_cert = None
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
try:
|
|
||||||
sock = socket.create_connection((self.host, self.port),
|
|
||||||
self.timeout, self.source_address)
|
|
||||||
except AttributeError:
|
|
||||||
# python 2.6 doesn't have the source_address attribute
|
|
||||||
sock = socket.create_connection((self.host, self.port),
|
|
||||||
self.timeout)
|
|
||||||
if self._tunnel_host:
|
|
||||||
self.sock = sock
|
|
||||||
self._tunnel()
|
|
||||||
|
|
||||||
if self.combined_cert:
|
|
||||||
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
|
|
||||||
cert_reqs=ssl.CERT_REQUIRED,
|
|
||||||
ca_certs=self.combined_cert)
|
|
||||||
else:
|
|
||||||
self.sock = ssl.wrap_socket(sock, self.key_file,
|
|
||||||
self.cert_file,
|
|
||||||
cert_reqs=ssl.CERT_NONE)
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
|
@ -1,188 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012, Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Mandeep Dhami, Big Switch Networks, Inc.
|
|
||||||
|
|
||||||
"""Test server mocking a REST based network ctrl.
|
|
||||||
|
|
||||||
Used for NeutronRestProxy tests
|
|
||||||
"""
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from six import moves
|
|
||||||
from wsgiref import simple_server
|
|
||||||
|
|
||||||
from neutron.openstack.common import jsonutils as json
|
|
||||||
|
|
||||||
|
|
||||||
class TestNetworkCtrl(object):
|
|
||||||
|
|
||||||
def __init__(self, host='', port=8000,
|
|
||||||
default_status='404 Not Found',
|
|
||||||
default_response='404 Not Found',
|
|
||||||
debug=False):
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.default_status = default_status
|
|
||||||
self.default_response = default_response
|
|
||||||
self.debug = debug
|
|
||||||
self.debug_env = False
|
|
||||||
self.debug_resp = False
|
|
||||||
self.matches = []
|
|
||||||
|
|
||||||
def match(self, prior, method_regexp, uri_regexp, handler, data=None,
|
|
||||||
multi=True):
|
|
||||||
"""Add to the list of exptected inputs.
|
|
||||||
|
|
||||||
The incoming request is matched in the order of priority. For same
|
|
||||||
priority, match the oldest match request first.
|
|
||||||
|
|
||||||
:param prior: intgere priority of this match (e.g. 100)
|
|
||||||
:param method_regexp: regexp to match method (e.g. 'PUT|POST')
|
|
||||||
:param uri_regexp: regexp to match uri (e.g. '/quantum/v?.?/')
|
|
||||||
:param handler: function with signature:
|
|
||||||
lambda(method, uri, body, **kwargs) : status, body
|
|
||||||
where
|
|
||||||
- method: HTTP method for this request
|
|
||||||
- uri: URI for this HTTP request
|
|
||||||
- body: body of this HTTP request
|
|
||||||
- kwargs are:
|
|
||||||
- data: data object that was in the match call
|
|
||||||
- node: TestNetworkCtrl object itself
|
|
||||||
- id: offset of the matching tuple
|
|
||||||
and return values is:
|
|
||||||
(status, body) where:
|
|
||||||
- status: HTTP resp status (e.g. '200 OK').
|
|
||||||
If None, use default_status
|
|
||||||
- body: HTTP resp body. If None, use ''
|
|
||||||
"""
|
|
||||||
assert int(prior) == prior, 'Priority should an integer be >= 0'
|
|
||||||
assert prior >= 0, 'Priority should an integer be >= 0'
|
|
||||||
|
|
||||||
lo, hi = 0, len(self.matches)
|
|
||||||
while lo < hi:
|
|
||||||
mid = (lo + hi) // 2
|
|
||||||
if prior < self.matches[mid]:
|
|
||||||
hi = mid
|
|
||||||
else:
|
|
||||||
lo = mid + 1
|
|
||||||
self.matches.insert(lo, (prior, method_regexp, uri_regexp, handler,
|
|
||||||
data, multi))
|
|
||||||
|
|
||||||
def remove_id(self, id_):
|
|
||||||
assert id_ >= 0, 'remove_id: id < 0'
|
|
||||||
assert id_ <= len(self.matches), 'remove_id: id > len()'
|
|
||||||
self.matches.pop(id_)
|
|
||||||
|
|
||||||
def request_handler(self, method, uri, body):
|
|
||||||
retstatus = self.default_status
|
|
||||||
retbody = self.default_response
|
|
||||||
for i in moves.xrange(len(self.matches)):
|
|
||||||
(prior, method_regexp, uri_regexp, handler, data, multi) = \
|
|
||||||
self.matches[i]
|
|
||||||
if re.match(method_regexp, method) and re.match(uri_regexp, uri):
|
|
||||||
kwargs = {
|
|
||||||
'data': data,
|
|
||||||
'node': self,
|
|
||||||
'id': i,
|
|
||||||
}
|
|
||||||
retstatus, retbody = handler(method, uri, body, **kwargs)
|
|
||||||
if multi is False:
|
|
||||||
self.remove_id(i)
|
|
||||||
break
|
|
||||||
if retbody is None:
|
|
||||||
retbody = ''
|
|
||||||
return (retstatus, retbody)
|
|
||||||
|
|
||||||
def server(self):
|
|
||||||
def app(environ, start_response):
|
|
||||||
uri = environ['PATH_INFO']
|
|
||||||
method = environ['REQUEST_METHOD']
|
|
||||||
headers = [('Content-type', 'text/json')]
|
|
||||||
content_len_str = environ['CONTENT_LENGTH']
|
|
||||||
|
|
||||||
content_len = 0
|
|
||||||
request_data = None
|
|
||||||
if content_len_str:
|
|
||||||
content_len = int(content_len_str)
|
|
||||||
request_data = environ.get('wsgi.input').read(content_len)
|
|
||||||
if request_data:
|
|
||||||
try:
|
|
||||||
request_data = json.loads(request_data)
|
|
||||||
except Exception:
|
|
||||||
# OK for it not to be json! Ignore it
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
print('\n')
|
|
||||||
if self.debug_env:
|
|
||||||
print('environ:')
|
|
||||||
for (key, value) in sorted(environ.iteritems()):
|
|
||||||
print(' %16s : %s' % (key, value))
|
|
||||||
|
|
||||||
print('%s %s' % (method, uri))
|
|
||||||
if request_data:
|
|
||||||
print('%s' %
|
|
||||||
json.dumps(request_data, sort_keys=True, indent=4))
|
|
||||||
|
|
||||||
status, body = self.request_handler(method, uri, None)
|
|
||||||
body_data = None
|
|
||||||
if body:
|
|
||||||
try:
|
|
||||||
body_data = json.loads(body)
|
|
||||||
except Exception:
|
|
||||||
# OK for it not to be json! Ignore it
|
|
||||||
pass
|
|
||||||
|
|
||||||
start_response(status, headers)
|
|
||||||
if self.debug:
|
|
||||||
if self.debug_env:
|
|
||||||
print('%s: %s' % ('Response',
|
|
||||||
json.dumps(body_data, sort_keys=True, indent=4)))
|
|
||||||
return body
|
|
||||||
return simple_server.make_server(self.host, self.port, app)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
print("Serving on port %d ..." % self.port)
|
|
||||||
try:
|
|
||||||
self.server().serve_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
|
|
||||||
port = 8899
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
port = int(sys.argv[1])
|
|
||||||
|
|
||||||
debug = False
|
|
||||||
if len(sys.argv) > 2:
|
|
||||||
if sys.argv[2].lower() in ['debug', 'true']:
|
|
||||||
debug = True
|
|
||||||
|
|
||||||
ctrl = TestNetworkCtrl(port=port,
|
|
||||||
default_status='200 OK',
|
|
||||||
default_response='{"status":"200 OK"}',
|
|
||||||
debug=debug)
|
|
||||||
ctrl.match(100, 'GET', '/test',
|
|
||||||
lambda m, u, b, **k: ('200 OK', '["200 OK"]'))
|
|
||||||
ctrl.run()
|
|
|
@ -1,27 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Big Switch Networks, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com
|
|
||||||
#
|
|
||||||
version_info = {'branch_nick': u'neutron/trunk',
|
|
||||||
'revision_id': u'1',
|
|
||||||
'revno': 0}
|
|
||||||
|
|
||||||
|
|
||||||
NEUTRONRESTPROXY_VERSION = ['2013', '1', None]
|
|
||||||
|
|
||||||
|
|
||||||
FINAL = False # This becomes true at Release Candidate time
|
|
|
@ -1,53 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012 OpenStack Foundation
|
|
||||||
# Copyright 2012, Big Switch Networks, 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.
|
|
||||||
#
|
|
||||||
# Based on openstack generic code
|
|
||||||
# @author: Mandeep Dhami, Big Switch Networks, Inc.
|
|
||||||
|
|
||||||
"""Determine version of NeutronRestProxy plugin"""
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from neutron.plugins.bigswitch import vcsversion
|
|
||||||
|
|
||||||
|
|
||||||
YEAR, COUNT, REVISION = vcsversion.NEUTRONRESTPROXY_VERSION
|
|
||||||
|
|
||||||
|
|
||||||
def canonical_version_string():
|
|
||||||
return '.'.join(filter(None,
|
|
||||||
vcsversion.NEUTRONRESTPROXY_VERSION))
|
|
||||||
|
|
||||||
|
|
||||||
def version_string():
|
|
||||||
if vcsversion.FINAL:
|
|
||||||
return canonical_version_string()
|
|
||||||
else:
|
|
||||||
return '%s-dev' % (canonical_version_string(),)
|
|
||||||
|
|
||||||
|
|
||||||
def vcs_version_string():
|
|
||||||
return "%s:%s" % (vcsversion.version_info['branch_nick'],
|
|
||||||
vcsversion.version_info['revision_id'])
|
|
||||||
|
|
||||||
|
|
||||||
def version_string_with_vcs():
|
|
||||||
return "%s-%s" % (canonical_version_string(), vcs_version_string())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(version_string_with_vcs())
|
|
|
@ -1,497 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
#
|
|
||||||
# (Some parts adapted from LinuxBridge Plugin)
|
|
||||||
# TODO(shiv) need support for security groups
|
|
||||||
|
|
||||||
|
|
||||||
"""Implentation of Brocade Neutron Plugin."""
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.agent import securitygroups_rpc as sg_rpc
|
|
||||||
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
|
|
||||||
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
|
|
||||||
from neutron.common import constants as q_const
|
|
||||||
from neutron.common import rpc_compat
|
|
||||||
from neutron.common import topics
|
|
||||||
from neutron.common import utils
|
|
||||||
from neutron.db import agents_db
|
|
||||||
from neutron.db import agentschedulers_db
|
|
||||||
from neutron.db import api as db
|
|
||||||
from neutron.db import db_base_plugin_v2
|
|
||||||
from neutron.db import dhcp_rpc_base
|
|
||||||
from neutron.db import external_net_db
|
|
||||||
from neutron.db import extraroute_db
|
|
||||||
from neutron.db import l3_agentschedulers_db
|
|
||||||
from neutron.db import l3_rpc_base
|
|
||||||
from neutron.db import portbindings_base
|
|
||||||
from neutron.db import securitygroups_rpc_base as sg_db_rpc
|
|
||||||
from neutron.extensions import portbindings
|
|
||||||
from neutron.extensions import securitygroup as ext_sg
|
|
||||||
from neutron.openstack.common import context
|
|
||||||
from neutron.openstack.common import importutils
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.brocade.db import models as brocade_db
|
|
||||||
from neutron.plugins.brocade import vlanbm as vbm
|
|
||||||
from neutron.plugins.common import constants as svc_constants
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
PLUGIN_VERSION = 0.88
|
|
||||||
AGENT_OWNER_PREFIX = "network:"
|
|
||||||
NOS_DRIVER = 'neutron.plugins.brocade.nos.nosdriver.NOSdriver'
|
|
||||||
|
|
||||||
SWITCH_OPTS = [cfg.StrOpt('address', default='',
|
|
||||||
help=_('The address of the host to SSH to')),
|
|
||||||
cfg.StrOpt('username', default='',
|
|
||||||
help=_('The SSH username to use')),
|
|
||||||
cfg.StrOpt('password', default='', secret=True,
|
|
||||||
help=_('The SSH password to use')),
|
|
||||||
cfg.StrOpt('ostype', default='NOS',
|
|
||||||
help=_('Currently unused'))
|
|
||||||
]
|
|
||||||
|
|
||||||
PHYSICAL_INTERFACE_OPTS = [cfg.StrOpt('physical_interface', default='eth0',
|
|
||||||
help=_('The network interface to use when creating'
|
|
||||||
'a port'))
|
|
||||||
]
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(SWITCH_OPTS, "SWITCH")
|
|
||||||
cfg.CONF.register_opts(PHYSICAL_INTERFACE_OPTS, "PHYSICAL_INTERFACE")
|
|
||||||
|
|
||||||
|
|
||||||
class BridgeRpcCallbacks(rpc_compat.RpcCallback,
|
|
||||||
dhcp_rpc_base.DhcpRpcCallbackMixin,
|
|
||||||
l3_rpc_base.L3RpcCallbackMixin,
|
|
||||||
sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
|
|
||||||
"""Agent callback."""
|
|
||||||
|
|
||||||
RPC_API_VERSION = '1.1'
|
|
||||||
# Device names start with "tap"
|
|
||||||
# history
|
|
||||||
# 1.1 Support Security Group RPC
|
|
||||||
TAP_PREFIX_LEN = 3
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_port_from_device(cls, device):
|
|
||||||
"""Get port from the brocade specific db."""
|
|
||||||
|
|
||||||
# TODO(shh) context is not being passed as
|
|
||||||
# an argument to this function;
|
|
||||||
#
|
|
||||||
# need to be fixed in:
|
|
||||||
# file: neutron/db/securtygroups_rpc_base.py
|
|
||||||
# function: securitygroup_rules_for_devices()
|
|
||||||
# which needs to pass context to us
|
|
||||||
|
|
||||||
# Doing what other plugins are doing
|
|
||||||
session = db.get_session()
|
|
||||||
port = brocade_db.get_port_from_device(
|
|
||||||
session, device[cls.TAP_PREFIX_LEN:])
|
|
||||||
|
|
||||||
# TODO(shiv): need to extend the db model to include device owners
|
|
||||||
# make it appears that the device owner is of type network
|
|
||||||
if port:
|
|
||||||
port['device'] = device
|
|
||||||
port['device_owner'] = AGENT_OWNER_PREFIX
|
|
||||||
port['binding:vif_type'] = 'bridge'
|
|
||||||
return port
|
|
||||||
|
|
||||||
def get_device_details(self, rpc_context, **kwargs):
|
|
||||||
"""Agent requests device details."""
|
|
||||||
|
|
||||||
agent_id = kwargs.get('agent_id')
|
|
||||||
device = kwargs.get('device')
|
|
||||||
LOG.debug(_("Device %(device)s details requested from %(agent_id)s"),
|
|
||||||
{'device': device, 'agent_id': agent_id})
|
|
||||||
port = brocade_db.get_port(rpc_context, device[self.TAP_PREFIX_LEN:])
|
|
||||||
if port:
|
|
||||||
entry = {'device': device,
|
|
||||||
'vlan_id': port.vlan_id,
|
|
||||||
'network_id': port.network_id,
|
|
||||||
'port_id': port.port_id,
|
|
||||||
'physical_network': port.physical_interface,
|
|
||||||
'admin_state_up': port.admin_state_up
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
|
||||||
entry = {'device': device}
|
|
||||||
LOG.debug(_("%s can not be found in database"), device)
|
|
||||||
return entry
|
|
||||||
|
|
||||||
def update_device_down(self, rpc_context, **kwargs):
|
|
||||||
"""Device no longer exists on agent."""
|
|
||||||
|
|
||||||
device = kwargs.get('device')
|
|
||||||
port = self.get_port_from_device(device)
|
|
||||||
if port:
|
|
||||||
entry = {'device': device,
|
|
||||||
'exists': True}
|
|
||||||
# Set port status to DOWN
|
|
||||||
port_id = port['port_id']
|
|
||||||
brocade_db.update_port_state(rpc_context, port_id, False)
|
|
||||||
else:
|
|
||||||
entry = {'device': device,
|
|
||||||
'exists': False}
|
|
||||||
LOG.debug(_("%s can not be found in database"), device)
|
|
||||||
return entry
|
|
||||||
|
|
||||||
|
|
||||||
class AgentNotifierApi(rpc_compat.RpcProxy,
|
|
||||||
sg_rpc.SecurityGroupAgentRpcApiMixin):
|
|
||||||
"""Agent side of the linux bridge rpc API.
|
|
||||||
|
|
||||||
API version history:
|
|
||||||
1.0 - Initial version.
|
|
||||||
1.1 - Added get_active_networks_info, create_dhcp_port,
|
|
||||||
and update_dhcp_port methods.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
BASE_RPC_API_VERSION = '1.1'
|
|
||||||
|
|
||||||
def __init__(self, topic):
|
|
||||||
super(AgentNotifierApi, self).__init__(
|
|
||||||
topic=topic, default_version=self.BASE_RPC_API_VERSION)
|
|
||||||
self.topic = topic
|
|
||||||
self.topic_network_delete = topics.get_topic_name(topic,
|
|
||||||
topics.NETWORK,
|
|
||||||
topics.DELETE)
|
|
||||||
self.topic_port_update = topics.get_topic_name(topic,
|
|
||||||
topics.PORT,
|
|
||||||
topics.UPDATE)
|
|
||||||
|
|
||||||
def network_delete(self, context, network_id):
|
|
||||||
self.fanout_cast(context,
|
|
||||||
self.make_msg('network_delete',
|
|
||||||
network_id=network_id),
|
|
||||||
topic=self.topic_network_delete)
|
|
||||||
|
|
||||||
def port_update(self, context, port, physical_network, vlan_id):
|
|
||||||
self.fanout_cast(context,
|
|
||||||
self.make_msg('port_update',
|
|
||||||
port=port,
|
|
||||||
physical_network=physical_network,
|
|
||||||
vlan_id=vlan_id),
|
|
||||||
topic=self.topic_port_update)
|
|
||||||
|
|
||||||
|
|
||||||
class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|
||||||
external_net_db.External_net_db_mixin,
|
|
||||||
extraroute_db.ExtraRoute_db_mixin,
|
|
||||||
sg_db_rpc.SecurityGroupServerRpcMixin,
|
|
||||||
l3_agentschedulers_db.L3AgentSchedulerDbMixin,
|
|
||||||
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
|
||||||
portbindings_base.PortBindingBaseMixin):
|
|
||||||
"""BrocadePluginV2 is a Neutron plugin.
|
|
||||||
|
|
||||||
Provides L2 Virtual Network functionality using VDX. Upper
|
|
||||||
layer driver class that interfaces to NETCONF layer below.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize Brocade Plugin.
|
|
||||||
|
|
||||||
Specify switch address and db configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
super(BrocadePluginV2, self).__init__()
|
|
||||||
self.supported_extension_aliases = ["binding", "security-group",
|
|
||||||
"external-net", "router",
|
|
||||||
"extraroute", "agent",
|
|
||||||
"l3_agent_scheduler",
|
|
||||||
"dhcp_agent_scheduler"]
|
|
||||||
|
|
||||||
self.physical_interface = (cfg.CONF.PHYSICAL_INTERFACE.
|
|
||||||
physical_interface)
|
|
||||||
self.base_binding_dict = self._get_base_binding_dict()
|
|
||||||
portbindings_base.register_port_dict_function()
|
|
||||||
self.ctxt = context.get_admin_context()
|
|
||||||
self.ctxt.session = db.get_session()
|
|
||||||
self._vlan_bitmap = vbm.VlanBitmap(self.ctxt)
|
|
||||||
self._setup_rpc()
|
|
||||||
self.network_scheduler = importutils.import_object(
|
|
||||||
cfg.CONF.network_scheduler_driver
|
|
||||||
)
|
|
||||||
self.router_scheduler = importutils.import_object(
|
|
||||||
cfg.CONF.router_scheduler_driver
|
|
||||||
)
|
|
||||||
self.brocade_init()
|
|
||||||
|
|
||||||
def brocade_init(self):
|
|
||||||
"""Brocade specific initialization."""
|
|
||||||
|
|
||||||
self._switch = {'address': cfg.CONF.SWITCH.address,
|
|
||||||
'username': cfg.CONF.SWITCH.username,
|
|
||||||
'password': cfg.CONF.SWITCH.password
|
|
||||||
}
|
|
||||||
self._driver = importutils.import_object(NOS_DRIVER)
|
|
||||||
|
|
||||||
def _setup_rpc(self):
|
|
||||||
# RPC support
|
|
||||||
self.service_topics = {svc_constants.CORE: topics.PLUGIN,
|
|
||||||
svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN}
|
|
||||||
self.rpc_context = context.RequestContext('neutron', 'neutron',
|
|
||||||
is_admin=False)
|
|
||||||
self.conn = rpc_compat.create_connection(new=True)
|
|
||||||
self.endpoints = [BridgeRpcCallbacks(),
|
|
||||||
agents_db.AgentExtRpcCallback()]
|
|
||||||
for svc_topic in self.service_topics.values():
|
|
||||||
self.conn.create_consumer(svc_topic, self.endpoints, fanout=False)
|
|
||||||
# Consume from all consumers in threads
|
|
||||||
self.conn.consume_in_threads()
|
|
||||||
self.notifier = AgentNotifierApi(topics.AGENT)
|
|
||||||
self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
|
|
||||||
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
|
|
||||||
)
|
|
||||||
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
|
|
||||||
l3_rpc_agent_api.L3AgentNotifyAPI()
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_network(self, context, network):
|
|
||||||
"""Create network.
|
|
||||||
|
|
||||||
This call to create network translates to creation of port-profile on
|
|
||||||
the physical switch.
|
|
||||||
"""
|
|
||||||
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
net = super(BrocadePluginV2, self).create_network(context, network)
|
|
||||||
net_uuid = net['id']
|
|
||||||
vlan_id = self._vlan_bitmap.get_next_vlan(None)
|
|
||||||
switch = self._switch
|
|
||||||
try:
|
|
||||||
self._driver.create_network(switch['address'],
|
|
||||||
switch['username'],
|
|
||||||
switch['password'],
|
|
||||||
vlan_id)
|
|
||||||
except Exception:
|
|
||||||
# Proper formatting
|
|
||||||
LOG.exception(_("Brocade NOS driver error"))
|
|
||||||
LOG.debug(_("Returning the allocated vlan (%d) to the pool"),
|
|
||||||
vlan_id)
|
|
||||||
self._vlan_bitmap.release_vlan(int(vlan_id))
|
|
||||||
raise Exception(_("Brocade plugin raised exception, "
|
|
||||||
"check logs"))
|
|
||||||
|
|
||||||
brocade_db.create_network(context, net_uuid, vlan_id)
|
|
||||||
self._process_l3_create(context, net, network['network'])
|
|
||||||
|
|
||||||
LOG.info(_("Allocated vlan (%d) from the pool"), vlan_id)
|
|
||||||
return net
|
|
||||||
|
|
||||||
def delete_network(self, context, net_id):
|
|
||||||
"""Delete network.
|
|
||||||
|
|
||||||
This call to delete the network translates to removing the
|
|
||||||
port-profile on the physical switch.
|
|
||||||
"""
|
|
||||||
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
self._process_l3_delete(context, net_id)
|
|
||||||
result = super(BrocadePluginV2, self).delete_network(context,
|
|
||||||
net_id)
|
|
||||||
# we must delete all ports in db first (foreign key constraint)
|
|
||||||
# there is no need to delete port in the driver (its a no-op)
|
|
||||||
# (actually: note there is no such call to the driver)
|
|
||||||
bports = brocade_db.get_ports(context, net_id)
|
|
||||||
for bport in bports:
|
|
||||||
brocade_db.delete_port(context, bport['port_id'])
|
|
||||||
|
|
||||||
# find the vlan for this network
|
|
||||||
net = brocade_db.get_network(context, net_id)
|
|
||||||
vlan_id = net['vlan']
|
|
||||||
|
|
||||||
# Tell hw to do remove PP
|
|
||||||
switch = self._switch
|
|
||||||
try:
|
|
||||||
self._driver.delete_network(switch['address'],
|
|
||||||
switch['username'],
|
|
||||||
switch['password'],
|
|
||||||
vlan_id)
|
|
||||||
except Exception:
|
|
||||||
# Proper formatting
|
|
||||||
LOG.exception(_("Brocade NOS driver error"))
|
|
||||||
raise Exception(_("Brocade plugin raised exception, "
|
|
||||||
"check logs"))
|
|
||||||
|
|
||||||
# now ok to delete the network
|
|
||||||
brocade_db.delete_network(context, net_id)
|
|
||||||
|
|
||||||
# relinquish vlan in bitmap
|
|
||||||
self._vlan_bitmap.release_vlan(int(vlan_id))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def update_network(self, context, id, network):
|
|
||||||
|
|
||||||
session = context.session
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
net = super(BrocadePluginV2, self).update_network(context, id,
|
|
||||||
network)
|
|
||||||
self._process_l3_update(context, net, network['network'])
|
|
||||||
return net
|
|
||||||
|
|
||||||
def create_port(self, context, port):
|
|
||||||
"""Create logical port on the switch."""
|
|
||||||
|
|
||||||
tenant_id = port['port']['tenant_id']
|
|
||||||
network_id = port['port']['network_id']
|
|
||||||
admin_state_up = port['port']['admin_state_up']
|
|
||||||
|
|
||||||
physical_interface = self.physical_interface
|
|
||||||
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
bnet = brocade_db.get_network(context, network_id)
|
|
||||||
vlan_id = bnet['vlan']
|
|
||||||
|
|
||||||
neutron_port = super(BrocadePluginV2, self).create_port(context,
|
|
||||||
port)
|
|
||||||
self._process_portbindings_create_and_update(context,
|
|
||||||
port['port'],
|
|
||||||
neutron_port)
|
|
||||||
interface_mac = neutron_port['mac_address']
|
|
||||||
port_id = neutron_port['id']
|
|
||||||
|
|
||||||
switch = self._switch
|
|
||||||
|
|
||||||
# convert mac format: xx:xx:xx:xx:xx:xx -> xxxx.xxxx.xxxx
|
|
||||||
mac = self.mac_reformat_62to34(interface_mac)
|
|
||||||
try:
|
|
||||||
self._driver.associate_mac_to_network(switch['address'],
|
|
||||||
switch['username'],
|
|
||||||
switch['password'],
|
|
||||||
vlan_id,
|
|
||||||
mac)
|
|
||||||
except Exception:
|
|
||||||
# Proper formatting
|
|
||||||
LOG.exception(_("Brocade NOS driver error"))
|
|
||||||
raise Exception(_("Brocade plugin raised exception, "
|
|
||||||
"check logs"))
|
|
||||||
|
|
||||||
# save to brocade persistent db
|
|
||||||
brocade_db.create_port(context, port_id, network_id,
|
|
||||||
physical_interface,
|
|
||||||
vlan_id, tenant_id, admin_state_up)
|
|
||||||
|
|
||||||
# apply any extensions
|
|
||||||
return neutron_port
|
|
||||||
|
|
||||||
def delete_port(self, context, port_id):
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
neutron_port = self.get_port(context, port_id)
|
|
||||||
interface_mac = neutron_port['mac_address']
|
|
||||||
# convert mac format: xx:xx:xx:xx:xx:xx -> xxxx.xxxx.xxxx
|
|
||||||
mac = self.mac_reformat_62to34(interface_mac)
|
|
||||||
|
|
||||||
brocade_port = brocade_db.get_port(context, port_id)
|
|
||||||
vlan_id = brocade_port['vlan_id']
|
|
||||||
|
|
||||||
switch = self._switch
|
|
||||||
try:
|
|
||||||
self._driver.dissociate_mac_from_network(switch['address'],
|
|
||||||
switch['username'],
|
|
||||||
switch['password'],
|
|
||||||
vlan_id,
|
|
||||||
mac)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_("Brocade NOS driver error"))
|
|
||||||
raise Exception(
|
|
||||||
_("Brocade plugin raised exception, check logs"))
|
|
||||||
|
|
||||||
super(BrocadePluginV2, self).delete_port(context, port_id)
|
|
||||||
brocade_db.delete_port(context, port_id)
|
|
||||||
|
|
||||||
def update_port(self, context, port_id, port):
|
|
||||||
original_port = self.get_port(context, port_id)
|
|
||||||
session = context.session
|
|
||||||
port_updated = False
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
# delete the port binding and read it with the new rules
|
|
||||||
if ext_sg.SECURITYGROUPS in port['port']:
|
|
||||||
port['port'][ext_sg.SECURITYGROUPS] = (
|
|
||||||
self._get_security_groups_on_port(context, port))
|
|
||||||
self._delete_port_security_group_bindings(context, port_id)
|
|
||||||
# process_port_create_security_group also needs port id
|
|
||||||
port['port']['id'] = port_id
|
|
||||||
self._process_port_create_security_group(
|
|
||||||
context,
|
|
||||||
port['port'],
|
|
||||||
port['port'][ext_sg.SECURITYGROUPS])
|
|
||||||
port_updated = True
|
|
||||||
port_data = port['port']
|
|
||||||
port = super(BrocadePluginV2, self).update_port(
|
|
||||||
context, port_id, port)
|
|
||||||
self._process_portbindings_create_and_update(context,
|
|
||||||
port_data,
|
|
||||||
port)
|
|
||||||
if original_port['admin_state_up'] != port['admin_state_up']:
|
|
||||||
port_updated = True
|
|
||||||
|
|
||||||
if (original_port['fixed_ips'] != port['fixed_ips'] or
|
|
||||||
not utils.compare_elements(
|
|
||||||
original_port.get(ext_sg.SECURITYGROUPS),
|
|
||||||
port.get(ext_sg.SECURITYGROUPS))):
|
|
||||||
self.notifier.security_groups_member_updated(
|
|
||||||
context, port.get(ext_sg.SECURITYGROUPS))
|
|
||||||
|
|
||||||
if port_updated:
|
|
||||||
self._notify_port_updated(context, port)
|
|
||||||
|
|
||||||
return port
|
|
||||||
|
|
||||||
def _notify_port_updated(self, context, port):
|
|
||||||
port_id = port['id']
|
|
||||||
bport = brocade_db.get_port(context, port_id)
|
|
||||||
self.notifier.port_update(context, port,
|
|
||||||
bport.physical_interface,
|
|
||||||
bport.vlan_id)
|
|
||||||
|
|
||||||
def _get_base_binding_dict(self):
|
|
||||||
binding = {
|
|
||||||
portbindings.VIF_TYPE: portbindings.VIF_TYPE_BRIDGE,
|
|
||||||
portbindings.VIF_DETAILS: {
|
|
||||||
# TODO(rkukura): Replace with new VIF security details
|
|
||||||
portbindings.CAP_PORT_FILTER:
|
|
||||||
'security-group' in self.supported_extension_aliases}}
|
|
||||||
return binding
|
|
||||||
|
|
||||||
def get_plugin_version(self):
|
|
||||||
"""Get version number of the plugin."""
|
|
||||||
return PLUGIN_VERSION
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mac_reformat_62to34(interface_mac):
|
|
||||||
"""Transform MAC address format.
|
|
||||||
|
|
||||||
Transforms from 6 groups of 2 hexadecimal numbers delimited by ":"
|
|
||||||
to 3 groups of 4 hexadecimals numbers delimited by ".".
|
|
||||||
|
|
||||||
:param interface_mac: MAC address in the format xx:xx:xx:xx:xx:xx
|
|
||||||
:type interface_mac: string
|
|
||||||
:returns: MAC address in the format xxxx.xxxx.xxxx
|
|
||||||
:rtype: string
|
|
||||||
"""
|
|
||||||
|
|
||||||
mac = interface_mac.replace(":", "")
|
|
||||||
mac = mac[0:4] + "." + mac[4:8] + "." + mac[8:12]
|
|
||||||
return mac
|
|
|
@ -1,112 +0,0 @@
|
||||||
Brocade Openstack Neutron Plugin
|
|
||||||
================================
|
|
||||||
|
|
||||||
* up-to-date version of these instructions are located at:
|
|
||||||
http://wiki.openstack.org/brocade-neutron-plugin
|
|
||||||
|
|
||||||
* N.B.: Please see Prerequisites section regarding ncclient (netconf client library)
|
|
||||||
|
|
||||||
* Supports VCS (Virtual Cluster of Switches)
|
|
||||||
|
|
||||||
|
|
||||||
Openstack Brocade Neutron Plugin implements the Neutron v2.0 API.
|
|
||||||
|
|
||||||
This plugin is meant to orchestrate Brocade VCS switches running NOS, examples of these are:
|
|
||||||
|
|
||||||
1. VDX 67xx series of switches
|
|
||||||
2. VDX 87xx series of switches
|
|
||||||
|
|
||||||
Brocade Neutron plugin implements the Neutron v2.0 API. It uses NETCONF at the backend
|
|
||||||
to configure the Brocade switch.
|
|
||||||
|
|
||||||
+------------+ +------------+ +-------------+
|
|
||||||
| | | | | |
|
|
||||||
| | | | | Brocade |
|
|
||||||
| Openstack | v2.0 | Brocade | NETCONF | VCS Switch |
|
|
||||||
| Neutron +--------+ Neutron +----------+ |
|
|
||||||
| | | Plugin | | VDX 67xx |
|
|
||||||
| | | | | VDX 87xx |
|
|
||||||
| | | | | |
|
|
||||||
| | | | | |
|
|
||||||
+------------+ +------------+ +-------------+
|
|
||||||
|
|
||||||
|
|
||||||
Directory Structure
|
|
||||||
===================
|
|
||||||
|
|
||||||
Normally you will have your Openstack directory structure as follows:
|
|
||||||
|
|
||||||
/opt/stack/nova/
|
|
||||||
/opt/stack/horizon/
|
|
||||||
...
|
|
||||||
/opt/stack/neutron/neutron/plugins/
|
|
||||||
|
|
||||||
Within this structure, Brocade plugin resides at:
|
|
||||||
|
|
||||||
/opt/stack/neutron/neutron/plugins/brocade
|
|
||||||
|
|
||||||
|
|
||||||
Prerequsites
|
|
||||||
============
|
|
||||||
|
|
||||||
This plugin requires installation of the python netconf client (ncclient) library:
|
|
||||||
|
|
||||||
ncclient v0.3.1 - Python library for NETCONF clients available at http://github.com/brocade/ncclient
|
|
||||||
|
|
||||||
% git clone https://www.github.com/brocade/ncclient
|
|
||||||
% cd ncclient; sudo python ./setup.py install
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
1. Specify to Neutron that you will be using the Brocade Plugin - this is done
|
|
||||||
by setting the parameter core_plugin in Neutron:
|
|
||||||
|
|
||||||
core_plugin = neutron.plugins.brocade.NeutronPlugin.BrocadePluginV2
|
|
||||||
|
|
||||||
2. Physical switch configuration parameters and Brocade specific database configuration is specified in
|
|
||||||
the configuration file specified in the brocade.ini files:
|
|
||||||
|
|
||||||
% cat /etc/neutron/plugins/brocade/brocade.ini
|
|
||||||
[SWITCH]
|
|
||||||
username = admin
|
|
||||||
password = password
|
|
||||||
address = <switch mgmt ip address>
|
|
||||||
ostype = NOS
|
|
||||||
|
|
||||||
[database]
|
|
||||||
connection = mysql://root:pass@localhost/brocade_neutron?charset=utf8
|
|
||||||
|
|
||||||
(please see list of more configuration parameters in the brocade.ini file)
|
|
||||||
|
|
||||||
Running Setup.py
|
|
||||||
================
|
|
||||||
|
|
||||||
Running setup.py with appropriate permissions will copy the default configuration
|
|
||||||
file to /etc/neutron/plugins/brocade/brocade.ini. This file MUST be edited to
|
|
||||||
suit your setup/environment.
|
|
||||||
|
|
||||||
% cd /opt/stack/neutron/neutron/plugins/brocade
|
|
||||||
% python setup.py
|
|
||||||
|
|
||||||
|
|
||||||
Devstack
|
|
||||||
========
|
|
||||||
|
|
||||||
Please see special notes for devstack at:
|
|
||||||
http://wiki.openstack.org/brocade-neutron-plugin
|
|
||||||
|
|
||||||
In order to use Brocade Neutron Plugin, add the following lines in localrc, if localrc file doe
|
|
||||||
not exist create one:
|
|
||||||
|
|
||||||
ENABLED_SERVICES=g-api,g-reg,key,n-api,n-crt,n-obj,n-cpu,n-net,n-cond,cinder,c-sch,c-api,c-vol,n-sch,n-novnc,n-xvnc,n-cauth,horizon,rabbit,neutron,q-svc,q-agt
|
|
||||||
Q_PLUGIN=brocade
|
|
||||||
|
|
||||||
As part of running devstack/stack.sh, the configuration files is copied as:
|
|
||||||
|
|
||||||
% cp /opt/stack/neutron/etc/neutron/plugins/brocade/brocade.ini /etc/neutron/plugins/brocade/brocade.ini
|
|
||||||
|
|
||||||
(hence it is important to make any changes to the configuration in:
|
|
||||||
/opt/stack/neutron/etc/neutron/plugins/brocade/brocade.ini)
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
|
@ -1,151 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""Brocade specific database schema/model."""
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from neutron.db import model_base
|
|
||||||
from neutron.db import models_v2
|
|
||||||
|
|
||||||
|
|
||||||
class BrocadeNetwork(model_base.BASEV2, models_v2.HasId):
|
|
||||||
"""Schema for brocade network."""
|
|
||||||
|
|
||||||
vlan = sa.Column(sa.String(10))
|
|
||||||
|
|
||||||
|
|
||||||
class BrocadePort(model_base.BASEV2):
|
|
||||||
"""Schema for brocade port."""
|
|
||||||
|
|
||||||
port_id = sa.Column(sa.String(36), primary_key=True, default="")
|
|
||||||
network_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey("brocadenetworks.id"),
|
|
||||||
nullable=False)
|
|
||||||
admin_state_up = sa.Column(sa.Boolean, nullable=False)
|
|
||||||
physical_interface = sa.Column(sa.String(36))
|
|
||||||
vlan_id = sa.Column(sa.String(36))
|
|
||||||
tenant_id = sa.Column(sa.String(36))
|
|
||||||
|
|
||||||
|
|
||||||
def create_network(context, net_id, vlan):
|
|
||||||
"""Create a brocade specific network/port-profiles."""
|
|
||||||
|
|
||||||
session = context.session
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
net = BrocadeNetwork(id=net_id, vlan=vlan)
|
|
||||||
session.add(net)
|
|
||||||
|
|
||||||
return net
|
|
||||||
|
|
||||||
|
|
||||||
def delete_network(context, net_id):
|
|
||||||
"""Delete a brocade specific network/port-profiles."""
|
|
||||||
|
|
||||||
session = context.session
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
net = (session.query(BrocadeNetwork).filter_by(id=net_id).first())
|
|
||||||
if net is not None:
|
|
||||||
session.delete(net)
|
|
||||||
|
|
||||||
|
|
||||||
def get_network(context, net_id, fields=None):
|
|
||||||
"""Get brocade specific network, with vlan extension."""
|
|
||||||
|
|
||||||
session = context.session
|
|
||||||
return (session.query(BrocadeNetwork).filter_by(id=net_id).first())
|
|
||||||
|
|
||||||
|
|
||||||
def get_networks(context, filters=None, fields=None):
|
|
||||||
"""Get all brocade specific networks."""
|
|
||||||
|
|
||||||
session = context.session
|
|
||||||
try:
|
|
||||||
nets = session.query(BrocadeNetwork).all()
|
|
||||||
return nets
|
|
||||||
except sa.exc.SQLAlchemyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def create_port(context, port_id, network_id, physical_interface,
|
|
||||||
vlan_id, tenant_id, admin_state_up):
|
|
||||||
"""Create a brocade specific port, has policy like vlan."""
|
|
||||||
|
|
||||||
# port_id is truncated: since the linux-bridge tap device names are
|
|
||||||
# based on truncated port id, this enables port lookups using
|
|
||||||
# tap devices
|
|
||||||
port_id = port_id[0:11]
|
|
||||||
session = context.session
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
port = BrocadePort(port_id=port_id,
|
|
||||||
network_id=network_id,
|
|
||||||
physical_interface=physical_interface,
|
|
||||||
vlan_id=vlan_id,
|
|
||||||
admin_state_up=admin_state_up,
|
|
||||||
tenant_id=tenant_id)
|
|
||||||
session.add(port)
|
|
||||||
return port
|
|
||||||
|
|
||||||
|
|
||||||
def get_port(context, port_id):
|
|
||||||
"""get a brocade specific port."""
|
|
||||||
|
|
||||||
port_id = port_id[0:11]
|
|
||||||
session = context.session
|
|
||||||
port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
|
|
||||||
return port
|
|
||||||
|
|
||||||
|
|
||||||
def get_ports(context, network_id=None):
|
|
||||||
"""get a brocade specific port."""
|
|
||||||
|
|
||||||
session = context.session
|
|
||||||
ports = (session.query(BrocadePort).filter_by(network_id=network_id).all())
|
|
||||||
return ports
|
|
||||||
|
|
||||||
|
|
||||||
def delete_port(context, port_id):
|
|
||||||
"""delete brocade specific port."""
|
|
||||||
|
|
||||||
port_id = port_id[0:11]
|
|
||||||
session = context.session
|
|
||||||
with session.begin(subtransactions=True):
|
|
||||||
port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
|
|
||||||
if port is not None:
|
|
||||||
session.delete(port)
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_from_device(session, port_id):
|
|
||||||
"""get port from the tap device."""
|
|
||||||
|
|
||||||
# device is same as truncated port_id
|
|
||||||
port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
|
|
||||||
return port
|
|
||||||
|
|
||||||
|
|
||||||
def update_port_state(context, port_id, admin_state_up):
|
|
||||||
"""Update port attributes."""
|
|
||||||
|
|
||||||
port_id = port_id[0:11]
|
|
||||||
session = context.session
|
|
||||||
session.query(BrocadePort).filter_by(
|
|
||||||
port_id=port_id).update({'admin_state_up': admin_state_up})
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Brocade Communications 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
|
@ -1,117 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""FAKE DRIVER, for unit tests purposes.
|
|
||||||
|
|
||||||
Brocade NOS Driver implements NETCONF over SSHv2 for
|
|
||||||
Neutron network life-cycle management.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NOSdriver():
|
|
||||||
"""NOS NETCONF interface driver for Neutron network.
|
|
||||||
|
|
||||||
Fake: Handles life-cycle management of Neutron network,
|
|
||||||
leverages AMPP on NOS
|
|
||||||
(for use by unit tests, avoids touching any hardware)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def connect(self, host, username, password):
|
|
||||||
"""Connect via SSH and initialize the NETCONF session."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_network(self, host, username, password, net_id):
|
|
||||||
"""Creates a new virtual network."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_network(self, host, username, password, net_id):
|
|
||||||
"""Deletes a virtual network."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def associate_mac_to_network(self, host, username, password,
|
|
||||||
net_id, mac):
|
|
||||||
"""Associates a MAC address to virtual network."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def dissociate_mac_from_network(self, host, username, password,
|
|
||||||
net_id, mac):
|
|
||||||
"""Dissociates a MAC address from virtual network."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_vlan_interface(self, mgr, vlan_id):
|
|
||||||
"""Configures a VLAN interface."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_vlan_interface(self, mgr, vlan_id):
|
|
||||||
"""Deletes a VLAN interface."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_port_profiles(self, mgr):
|
|
||||||
"""Retrieves all port profiles."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_port_profile(self, mgr, name):
|
|
||||||
"""Retrieves a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_port_profile(self, mgr, name):
|
|
||||||
"""Creates a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_port_profile(self, mgr, name):
|
|
||||||
"""Deletes a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def activate_port_profile(self, mgr, name):
|
|
||||||
"""Activates a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def deactivate_port_profile(self, mgr, name):
|
|
||||||
"""Deactivates a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def associate_mac_to_port_profile(self, mgr, name, mac_address):
|
|
||||||
"""Associates a MAC address to a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def dissociate_mac_from_port_profile(self, mgr, name, mac_address):
|
|
||||||
"""Dissociates a MAC address from a port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_vlan_profile_for_port_profile(self, mgr, name):
|
|
||||||
"""Creates VLAN sub-profile for port profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def configure_l2_mode_for_vlan_profile(self, mgr, name):
|
|
||||||
"""Configures L2 mode for VLAN sub-profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def configure_trunk_mode_for_vlan_profile(self, mgr, name):
|
|
||||||
"""Configures trunk mode for VLAN sub-profile."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def configure_allowed_vlans_for_vlan_profile(self, mgr, name, vlan_id):
|
|
||||||
"""Configures allowed VLANs for VLAN sub-profile."""
|
|
||||||
pass
|
|
|
@ -1,204 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Brocade Communications 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""NOS NETCONF XML Configuration Command Templates.
|
|
||||||
|
|
||||||
Interface Configuration Commands
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Create VLAN (vlan_id)
|
|
||||||
CREATE_VLAN_INTERFACE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<interface-vlan xmlns="urn:brocade.com:mgmt:brocade-interface">
|
|
||||||
<interface>
|
|
||||||
<vlan>
|
|
||||||
<name>{vlan_id}</name>
|
|
||||||
</vlan>
|
|
||||||
</interface>
|
|
||||||
</interface-vlan>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Delete VLAN (vlan_id)
|
|
||||||
DELETE_VLAN_INTERFACE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<interface-vlan xmlns="urn:brocade.com:mgmt:brocade-interface">
|
|
||||||
<interface>
|
|
||||||
<vlan operation="delete">
|
|
||||||
<name>{vlan_id}</name>
|
|
||||||
</vlan>
|
|
||||||
</interface>
|
|
||||||
</interface-vlan>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
#
|
|
||||||
# AMPP Life-cycle Management Configuration Commands
|
|
||||||
#
|
|
||||||
|
|
||||||
# Create AMPP port-profile (port_profile_name)
|
|
||||||
CREATE_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<name>{name}</name>
|
|
||||||
</port-profile>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Create VLAN sub-profile for port-profile (port_profile_name)
|
|
||||||
CREATE_VLAN_PROFILE_FOR_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<name>{name}</name>
|
|
||||||
<vlan-profile/>
|
|
||||||
</port-profile>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Configure L2 mode for VLAN sub-profile (port_profile_name)
|
|
||||||
CONFIGURE_L2_MODE_FOR_VLAN_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<name>{name}</name>
|
|
||||||
<vlan-profile>
|
|
||||||
<switchport/>
|
|
||||||
</vlan-profile>
|
|
||||||
</port-profile>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Configure trunk mode for VLAN sub-profile (port_profile_name)
|
|
||||||
CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<name>{name}</name>
|
|
||||||
<vlan-profile>
|
|
||||||
<switchport>
|
|
||||||
<mode>
|
|
||||||
<vlan-mode>trunk</vlan-mode>
|
|
||||||
</mode>
|
|
||||||
</switchport>
|
|
||||||
</vlan-profile>
|
|
||||||
</port-profile>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Configure allowed VLANs for VLAN sub-profile
|
|
||||||
# (port_profile_name, allowed_vlan, native_vlan)
|
|
||||||
CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<name>{name}</name>
|
|
||||||
<vlan-profile>
|
|
||||||
<switchport>
|
|
||||||
<trunk>
|
|
||||||
<allowed>
|
|
||||||
<vlan>
|
|
||||||
<add>{vlan_id}</add>
|
|
||||||
</vlan>
|
|
||||||
</allowed>
|
|
||||||
</trunk>
|
|
||||||
</switchport>
|
|
||||||
</vlan-profile>
|
|
||||||
</port-profile>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Delete port-profile (port_profile_name)
|
|
||||||
DELETE_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile
|
|
||||||
xmlns="urn:brocade.com:mgmt:brocade-port-profile" operation="delete">
|
|
||||||
<name>{name}</name>
|
|
||||||
</port-profile>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Activate port-profile (port_profile_name)
|
|
||||||
ACTIVATE_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<port-profile>
|
|
||||||
<name>{name}</name>
|
|
||||||
<activate/>
|
|
||||||
</port-profile>
|
|
||||||
</port-profile-global>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Deactivate port-profile (port_profile_name)
|
|
||||||
DEACTIVATE_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<port-profile>
|
|
||||||
<name>{name}</name>
|
|
||||||
<activate
|
|
||||||
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete" />
|
|
||||||
</port-profile>
|
|
||||||
</port-profile-global>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Associate MAC address to port-profile (port_profile_name, mac_address)
|
|
||||||
ASSOCIATE_MAC_TO_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<port-profile>
|
|
||||||
<name>{name}</name>
|
|
||||||
<static>
|
|
||||||
<mac-address>{mac_address}</mac-address>
|
|
||||||
</static>
|
|
||||||
</port-profile>
|
|
||||||
</port-profile-global>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Dissociate MAC address from port-profile (port_profile_name, mac_address)
|
|
||||||
DISSOCIATE_MAC_FROM_PORT_PROFILE = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
|
|
||||||
<port-profile>
|
|
||||||
<name>{name}</name>
|
|
||||||
<static
|
|
||||||
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">
|
|
||||||
<mac-address>{mac_address}</mac-address>
|
|
||||||
</static>
|
|
||||||
</port-profile>
|
|
||||||
</port-profile-global>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
#
|
|
||||||
# Custom RPC Commands
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Constants
|
|
||||||
#
|
|
||||||
|
|
||||||
# Port profile naming convention for Neutron networks
|
|
||||||
OS_PORT_PROFILE_NAME = "openstack-profile-{id}"
|
|
||||||
|
|
||||||
# Port profile filter expressions
|
|
||||||
PORT_PROFILE_XPATH_FILTER = "/port-profile"
|
|
||||||
PORT_PROFILE_NAME_XPATH_FILTER = "/port-profile[name='{name}']"
|
|
|
@ -1,233 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""Brocade NOS Driver implements NETCONF over SSHv2 for
|
|
||||||
Neutron network life-cycle management.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ncclient import manager
|
|
||||||
|
|
||||||
from neutron.openstack.common import excutils
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.brocade.nos import nctemplates as template
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
SSH_PORT = 22
|
|
||||||
|
|
||||||
|
|
||||||
def nos_unknown_host_cb(host, fingerprint):
|
|
||||||
"""An unknown host callback.
|
|
||||||
|
|
||||||
Returns `True` if it finds the key acceptable,
|
|
||||||
and `False` if not. This default callback for NOS always returns 'True'
|
|
||||||
(i.e. trusts all hosts for now).
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class NOSdriver():
|
|
||||||
"""NOS NETCONF interface driver for Neutron network.
|
|
||||||
|
|
||||||
Handles life-cycle management of Neutron network (leverages AMPP on NOS)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.mgr = None
|
|
||||||
|
|
||||||
def connect(self, host, username, password):
|
|
||||||
"""Connect via SSH and initialize the NETCONF session."""
|
|
||||||
|
|
||||||
# Use the persisted NETCONF connection
|
|
||||||
if self.mgr and self.mgr.connected:
|
|
||||||
return self.mgr
|
|
||||||
|
|
||||||
# Open new NETCONF connection
|
|
||||||
try:
|
|
||||||
self.mgr = manager.connect(host=host, port=SSH_PORT,
|
|
||||||
username=username, password=password,
|
|
||||||
unknown_host_cb=nos_unknown_host_cb)
|
|
||||||
except Exception as e:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.error(_("Connect failed to switch: %s"), e)
|
|
||||||
|
|
||||||
LOG.debug(_("Connect success to host %(host)s:%(ssh_port)d"),
|
|
||||||
dict(host=host, ssh_port=SSH_PORT))
|
|
||||||
return self.mgr
|
|
||||||
|
|
||||||
def close_session(self):
|
|
||||||
"""Close NETCONF session."""
|
|
||||||
if self.mgr:
|
|
||||||
self.mgr.close_session()
|
|
||||||
self.mgr = None
|
|
||||||
|
|
||||||
def create_network(self, host, username, password, net_id):
|
|
||||||
"""Creates a new virtual network."""
|
|
||||||
|
|
||||||
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
|
||||||
try:
|
|
||||||
mgr = self.connect(host, username, password)
|
|
||||||
self.create_vlan_interface(mgr, net_id)
|
|
||||||
self.create_port_profile(mgr, name)
|
|
||||||
self.create_vlan_profile_for_port_profile(mgr, name)
|
|
||||||
self.configure_l2_mode_for_vlan_profile(mgr, name)
|
|
||||||
self.configure_trunk_mode_for_vlan_profile(mgr, name)
|
|
||||||
self.configure_allowed_vlans_for_vlan_profile(mgr, name, net_id)
|
|
||||||
self.activate_port_profile(mgr, name)
|
|
||||||
except Exception as ex:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_("NETCONF error: %s"), ex)
|
|
||||||
self.close_session()
|
|
||||||
|
|
||||||
def delete_network(self, host, username, password, net_id):
|
|
||||||
"""Deletes a virtual network."""
|
|
||||||
|
|
||||||
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
|
||||||
try:
|
|
||||||
mgr = self.connect(host, username, password)
|
|
||||||
self.deactivate_port_profile(mgr, name)
|
|
||||||
self.delete_port_profile(mgr, name)
|
|
||||||
self.delete_vlan_interface(mgr, net_id)
|
|
||||||
except Exception as ex:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_("NETCONF error: %s"), ex)
|
|
||||||
self.close_session()
|
|
||||||
|
|
||||||
def associate_mac_to_network(self, host, username, password,
|
|
||||||
net_id, mac):
|
|
||||||
"""Associates a MAC address to virtual network."""
|
|
||||||
|
|
||||||
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
|
||||||
try:
|
|
||||||
mgr = self.connect(host, username, password)
|
|
||||||
self.associate_mac_to_port_profile(mgr, name, mac)
|
|
||||||
except Exception as ex:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_("NETCONF error: %s"), ex)
|
|
||||||
self.close_session()
|
|
||||||
|
|
||||||
def dissociate_mac_from_network(self, host, username, password,
|
|
||||||
net_id, mac):
|
|
||||||
"""Dissociates a MAC address from virtual network."""
|
|
||||||
|
|
||||||
name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
|
|
||||||
try:
|
|
||||||
mgr = self.connect(host, username, password)
|
|
||||||
self.dissociate_mac_from_port_profile(mgr, name, mac)
|
|
||||||
except Exception as ex:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_("NETCONF error: %s"), ex)
|
|
||||||
self.close_session()
|
|
||||||
|
|
||||||
def create_vlan_interface(self, mgr, vlan_id):
|
|
||||||
"""Configures a VLAN interface."""
|
|
||||||
|
|
||||||
confstr = template.CREATE_VLAN_INTERFACE.format(vlan_id=vlan_id)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def delete_vlan_interface(self, mgr, vlan_id):
|
|
||||||
"""Deletes a VLAN interface."""
|
|
||||||
|
|
||||||
confstr = template.DELETE_VLAN_INTERFACE.format(vlan_id=vlan_id)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def get_port_profiles(self, mgr):
|
|
||||||
"""Retrieves all port profiles."""
|
|
||||||
|
|
||||||
filterstr = template.PORT_PROFILE_XPATH_FILTER
|
|
||||||
response = mgr.get_config(source='running',
|
|
||||||
filter=('xpath', filterstr)).data_xml
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_port_profile(self, mgr, name):
|
|
||||||
"""Retrieves a port profile."""
|
|
||||||
|
|
||||||
filterstr = template.PORT_PROFILE_NAME_XPATH_FILTER.format(name=name)
|
|
||||||
response = mgr.get_config(source='running',
|
|
||||||
filter=('xpath', filterstr)).data_xml
|
|
||||||
return response
|
|
||||||
|
|
||||||
def create_port_profile(self, mgr, name):
|
|
||||||
"""Creates a port profile."""
|
|
||||||
|
|
||||||
confstr = template.CREATE_PORT_PROFILE.format(name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def delete_port_profile(self, mgr, name):
|
|
||||||
"""Deletes a port profile."""
|
|
||||||
|
|
||||||
confstr = template.DELETE_PORT_PROFILE.format(name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def activate_port_profile(self, mgr, name):
|
|
||||||
"""Activates a port profile."""
|
|
||||||
|
|
||||||
confstr = template.ACTIVATE_PORT_PROFILE.format(name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def deactivate_port_profile(self, mgr, name):
|
|
||||||
"""Deactivates a port profile."""
|
|
||||||
|
|
||||||
confstr = template.DEACTIVATE_PORT_PROFILE.format(name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def associate_mac_to_port_profile(self, mgr, name, mac_address):
|
|
||||||
"""Associates a MAC address to a port profile."""
|
|
||||||
|
|
||||||
confstr = template.ASSOCIATE_MAC_TO_PORT_PROFILE.format(
|
|
||||||
name=name, mac_address=mac_address)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def dissociate_mac_from_port_profile(self, mgr, name, mac_address):
|
|
||||||
"""Dissociates a MAC address from a port profile."""
|
|
||||||
|
|
||||||
confstr = template.DISSOCIATE_MAC_FROM_PORT_PROFILE.format(
|
|
||||||
name=name, mac_address=mac_address)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def create_vlan_profile_for_port_profile(self, mgr, name):
|
|
||||||
"""Creates VLAN sub-profile for port profile."""
|
|
||||||
|
|
||||||
confstr = template.CREATE_VLAN_PROFILE_FOR_PORT_PROFILE.format(
|
|
||||||
name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def configure_l2_mode_for_vlan_profile(self, mgr, name):
|
|
||||||
"""Configures L2 mode for VLAN sub-profile."""
|
|
||||||
|
|
||||||
confstr = template.CONFIGURE_L2_MODE_FOR_VLAN_PROFILE.format(
|
|
||||||
name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def configure_trunk_mode_for_vlan_profile(self, mgr, name):
|
|
||||||
"""Configures trunk mode for VLAN sub-profile."""
|
|
||||||
|
|
||||||
confstr = template.CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE.format(
|
|
||||||
name=name)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
||||||
|
|
||||||
def configure_allowed_vlans_for_vlan_profile(self, mgr, name, vlan_id):
|
|
||||||
"""Configures allowed VLANs for VLAN sub-profile."""
|
|
||||||
|
|
||||||
confstr = template.CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE.format(
|
|
||||||
name=name, vlan_id=vlan_id)
|
|
||||||
mgr.edit_config(target='running', config=confstr)
|
|
|
@ -1,24 +0,0 @@
|
||||||
Start the neutron-server with IP address of switch configured in brocade.ini:
|
|
||||||
(for configuration instruction please see README.md in the above directory)
|
|
||||||
|
|
||||||
nostest.py:
|
|
||||||
This tests two things:
|
|
||||||
1. Creates port-profile on the physical switch when a neutron 'network' is created
|
|
||||||
2. Associates the MAC address with the created port-profile
|
|
||||||
|
|
||||||
noscli.py:
|
|
||||||
CLI interface to create/delete/associate MAC/dissociate MAC
|
|
||||||
Commands:
|
|
||||||
% noscli.py create <network>
|
|
||||||
(after running check that PP is created on the switch)
|
|
||||||
|
|
||||||
% noscli.py delete <network>
|
|
||||||
(after running check that PP is deleted from the switch)
|
|
||||||
|
|
||||||
% noscli.py associate <network> <mac>
|
|
||||||
(after running check that MAC is associated with PP)
|
|
||||||
|
|
||||||
% noscli.py dissociate <network> <mac>
|
|
||||||
(after running check that MAC is dissociated from the PP)
|
|
||||||
|
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Brocade Communications 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""Brocade NOS Driver CLI."""
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.brocade.nos import nosdriver as nos
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class NOSCli(object):
|
|
||||||
|
|
||||||
def __init__(self, host, username, password):
|
|
||||||
self.host = host
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.driver = nos.NOSdriver()
|
|
||||||
|
|
||||||
def execute(self, cmd):
|
|
||||||
numargs = len(args.otherargs)
|
|
||||||
|
|
||||||
if args.cmd == 'create' and numargs == 1:
|
|
||||||
self._create(args.otherargs[0])
|
|
||||||
elif args.cmd == 'delete' and numargs == 1:
|
|
||||||
self._delete(args.otherargs[0])
|
|
||||||
elif args.cmd == 'associate' and numargs == 2:
|
|
||||||
self._associate(args.otherargs[0], args.otherargs[1])
|
|
||||||
elif args.cmd == 'dissociate' and numargs == 2:
|
|
||||||
self._dissociate(args.otherargs[0], args.otherargs[1])
|
|
||||||
else:
|
|
||||||
print(usage_desc)
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
def _create(self, net_id):
|
|
||||||
self.driver.create_network(self.host, self.username, self.password,
|
|
||||||
net_id)
|
|
||||||
|
|
||||||
def _delete(self, net_id):
|
|
||||||
self.driver.delete_network(self.host, self.username, self.password,
|
|
||||||
net_id)
|
|
||||||
|
|
||||||
def _associate(self, net_id, mac):
|
|
||||||
self.driver.associate_mac_to_network(
|
|
||||||
self.host, self.username, self.password, net_id, mac)
|
|
||||||
|
|
||||||
def _dissociate(self, net_id, mac):
|
|
||||||
self.driver.dissociate_mac_from_network(
|
|
||||||
self.host, self.username, self.password, net_id, mac)
|
|
||||||
|
|
||||||
|
|
||||||
usage_desc = """
|
|
||||||
Command descriptions:
|
|
||||||
|
|
||||||
create <id>
|
|
||||||
delete <id>
|
|
||||||
associate <id> <mac>
|
|
||||||
dissociate <id> <mac>
|
|
||||||
"""
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='process args',
|
|
||||||
usage=usage_desc, epilog='foo bar help')
|
|
||||||
parser.add_argument('--ip', default='localhost')
|
|
||||||
parser.add_argument('--username', default='admin')
|
|
||||||
parser.add_argument('--password', default='password')
|
|
||||||
parser.add_argument('cmd')
|
|
||||||
parser.add_argument('otherargs', nargs='*')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
noscli = NOSCli(args.ip, args.username, args.password)
|
|
||||||
noscli.execute(args.cmd)
|
|
|
@ -1,48 +0,0 @@
|
||||||
# Copyright (c) 2013 Brocade Communications 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""Brocade NOS Driver Test."""
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from neutron.plugins.brocade.nos import nosdriver as nos
|
|
||||||
|
|
||||||
|
|
||||||
def nostest(host, username, password):
|
|
||||||
# Driver
|
|
||||||
driver = nos.NOSdriver()
|
|
||||||
|
|
||||||
# Neutron operations
|
|
||||||
vlan = 1001
|
|
||||||
mac = '0050.56bf.0001'
|
|
||||||
driver.create_network(host, username, password, vlan)
|
|
||||||
driver.associate_mac_to_network(host, username, password, vlan, mac)
|
|
||||||
driver.dissociate_mac_from_network(host, username, password, vlan, mac)
|
|
||||||
driver.delete_network(host, username, password, vlan)
|
|
||||||
|
|
||||||
# AMPP enumeration
|
|
||||||
with driver.connect(host, username, password) as mgr:
|
|
||||||
print(driver.get_port_profiles(mgr))
|
|
||||||
print(driver.get_port_profile(mgr, 'default'))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
nostest(sys.argv[1], sys.argv[2], sys.argv[3])
|
|
|
@ -1,60 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2013 Brocade Communications System, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# Authors:
|
|
||||||
# Shiv Haris (sharis@brocade.com)
|
|
||||||
# Varma Bhupatiraju (vbhupati@#brocade.com)
|
|
||||||
|
|
||||||
|
|
||||||
"""A Vlan Bitmap class to handle allocation/de-allocation of vlan ids."""
|
|
||||||
from six import moves
|
|
||||||
|
|
||||||
from neutron.common import constants
|
|
||||||
from neutron.plugins.brocade.db import models as brocade_db
|
|
||||||
|
|
||||||
|
|
||||||
MIN_VLAN = constants.MIN_VLAN_TAG + 1
|
|
||||||
MAX_VLAN = constants.MAX_VLAN_TAG
|
|
||||||
|
|
||||||
|
|
||||||
class VlanBitmap(object):
|
|
||||||
"""Setup a vlan bitmap for allocation/de-allocation."""
|
|
||||||
|
|
||||||
# Keep track of the vlans that have been allocated/de-allocated
|
|
||||||
# uses a bitmap to do this
|
|
||||||
|
|
||||||
def __init__(self, ctxt):
|
|
||||||
"""Initialize the vlan as a set."""
|
|
||||||
self.vlans = set(int(net['vlan'])
|
|
||||||
for net in brocade_db.get_networks(ctxt)
|
|
||||||
if net['vlan']
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_next_vlan(self, vlan_id=None):
|
|
||||||
"""Try to get a specific vlan if requested or get the next vlan."""
|
|
||||||
min_vlan_search = vlan_id or MIN_VLAN
|
|
||||||
max_vlan_search = (vlan_id and vlan_id + 1) or MAX_VLAN
|
|
||||||
|
|
||||||
for vlan in moves.xrange(min_vlan_search, max_vlan_search):
|
|
||||||
if vlan not in self.vlans:
|
|
||||||
self.vlans.add(vlan)
|
|
||||||
return vlan
|
|
||||||
|
|
||||||
def release_vlan(self, vlan_id):
|
|
||||||
"""Return the vlan to the pool."""
|
|
||||||
if vlan_id in self.vlans:
|
|
||||||
self.vlans.remove(vlan_id)
|
|
|
@ -1,7 +0,0 @@
|
||||||
Cisco Neutron Virtual Network Plugin
|
|
||||||
|
|
||||||
This plugin implements Neutron v2 APIs and helps configure
|
|
||||||
topologies consisting of virtual and physical switches.
|
|
||||||
|
|
||||||
For more details on use please refer to:
|
|
||||||
http://wiki.openstack.org/cisco-neutron
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
#
|
|
|
@ -1,17 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
|
@ -1,111 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
# Attachment attributes
|
|
||||||
INSTANCE_ID = 'instance_id'
|
|
||||||
TENANT_ID = 'tenant_id'
|
|
||||||
TENANT_NAME = 'tenant_name'
|
|
||||||
HOST_NAME = 'host_name'
|
|
||||||
|
|
||||||
# Network attributes
|
|
||||||
NET_ID = 'id'
|
|
||||||
NET_NAME = 'name'
|
|
||||||
NET_VLAN_ID = 'vlan_id'
|
|
||||||
NET_VLAN_NAME = 'vlan_name'
|
|
||||||
NET_PORTS = 'ports'
|
|
||||||
|
|
||||||
CREDENTIAL_ID = 'credential_id'
|
|
||||||
CREDENTIAL_NAME = 'credential_name'
|
|
||||||
CREDENTIAL_USERNAME = 'user_name'
|
|
||||||
CREDENTIAL_PASSWORD = 'password'
|
|
||||||
CREDENTIAL_TYPE = 'type'
|
|
||||||
MASKED_PASSWORD = '********'
|
|
||||||
|
|
||||||
USERNAME = 'username'
|
|
||||||
PASSWORD = 'password'
|
|
||||||
|
|
||||||
LOGGER_COMPONENT_NAME = "cisco_plugin"
|
|
||||||
|
|
||||||
NEXUS_PLUGIN = 'nexus_plugin'
|
|
||||||
VSWITCH_PLUGIN = 'vswitch_plugin'
|
|
||||||
|
|
||||||
DEVICE_IP = 'device_ip'
|
|
||||||
|
|
||||||
NETWORK_ADMIN = 'network_admin'
|
|
||||||
|
|
||||||
NETWORK = 'network'
|
|
||||||
PORT = 'port'
|
|
||||||
BASE_PLUGIN_REF = 'base_plugin_ref'
|
|
||||||
CONTEXT = 'context'
|
|
||||||
SUBNET = 'subnet'
|
|
||||||
|
|
||||||
#### N1Kv CONSTANTS
|
|
||||||
# Special vlan_id value in n1kv_vlan_allocations table indicating flat network
|
|
||||||
FLAT_VLAN_ID = -1
|
|
||||||
|
|
||||||
# Topic for tunnel notifications between the plugin and agent
|
|
||||||
TUNNEL = 'tunnel'
|
|
||||||
|
|
||||||
# Maximum VXLAN range configurable for one network profile.
|
|
||||||
MAX_VXLAN_RANGE = 1000000
|
|
||||||
|
|
||||||
# Values for network_type
|
|
||||||
NETWORK_TYPE_FLAT = 'flat'
|
|
||||||
NETWORK_TYPE_VLAN = 'vlan'
|
|
||||||
NETWORK_TYPE_VXLAN = 'vxlan'
|
|
||||||
NETWORK_TYPE_LOCAL = 'local'
|
|
||||||
NETWORK_TYPE_NONE = 'none'
|
|
||||||
NETWORK_TYPE_TRUNK = 'trunk'
|
|
||||||
NETWORK_TYPE_MULTI_SEGMENT = 'multi-segment'
|
|
||||||
|
|
||||||
# Values for network sub_type
|
|
||||||
NETWORK_TYPE_OVERLAY = 'overlay'
|
|
||||||
NETWORK_SUBTYPE_NATIVE_VXLAN = 'native_vxlan'
|
|
||||||
NETWORK_SUBTYPE_TRUNK_VLAN = NETWORK_TYPE_VLAN
|
|
||||||
NETWORK_SUBTYPE_TRUNK_VXLAN = NETWORK_TYPE_OVERLAY
|
|
||||||
|
|
||||||
# Prefix for VM Network name
|
|
||||||
VM_NETWORK_NAME_PREFIX = 'vmn_'
|
|
||||||
|
|
||||||
DEFAULT_HTTP_TIMEOUT = 15
|
|
||||||
SET = 'set'
|
|
||||||
INSTANCE = 'instance'
|
|
||||||
PROPERTIES = 'properties'
|
|
||||||
NAME = 'name'
|
|
||||||
ID = 'id'
|
|
||||||
POLICY = 'policy'
|
|
||||||
TENANT_ID_NOT_SET = 'TENANT_ID_NOT_SET'
|
|
||||||
ENCAPSULATIONS = 'encapsulations'
|
|
||||||
STATE = 'state'
|
|
||||||
ONLINE = 'online'
|
|
||||||
MAPPINGS = 'mappings'
|
|
||||||
MAPPING = 'mapping'
|
|
||||||
SEGMENTS = 'segments'
|
|
||||||
SEGMENT = 'segment'
|
|
||||||
BRIDGE_DOMAIN_SUFFIX = '_bd'
|
|
||||||
LOGICAL_NETWORK_SUFFIX = '_log_net'
|
|
||||||
ENCAPSULATION_PROFILE_SUFFIX = '_profile'
|
|
||||||
|
|
||||||
UUID_LENGTH = 36
|
|
||||||
|
|
||||||
# Nexus vlan and vxlan segment range
|
|
||||||
NEXUS_VLAN_RESERVED_MIN = 3968
|
|
||||||
NEXUS_VLAN_RESERVED_MAX = 4047
|
|
||||||
NEXUS_VXLAN_MIN = 4096
|
|
||||||
NEXUS_VXLAN_MAX = 16000000
|
|
|
@ -1,61 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import logging as LOG
|
|
||||||
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants as const
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as cexc
|
|
||||||
from neutron.plugins.cisco.common import config
|
|
||||||
from neutron.plugins.cisco.db import network_db_v2 as cdb
|
|
||||||
|
|
||||||
LOG.basicConfig(level=LOG.WARN)
|
|
||||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
|
||||||
|
|
||||||
|
|
||||||
class Store(object):
|
|
||||||
"""Credential Store."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def initialize():
|
|
||||||
dev_dict = config.get_device_dictionary()
|
|
||||||
for key in dev_dict:
|
|
||||||
dev_id, dev_ip, dev_key = key
|
|
||||||
if dev_key == const.USERNAME:
|
|
||||||
try:
|
|
||||||
cdb.add_credential(
|
|
||||||
dev_ip,
|
|
||||||
dev_dict[dev_id, dev_ip, const.USERNAME],
|
|
||||||
dev_dict[dev_id, dev_ip, const.PASSWORD],
|
|
||||||
dev_id)
|
|
||||||
except cexc.CredentialAlreadyExists:
|
|
||||||
# We are quietly ignoring this, since it only happens
|
|
||||||
# if this class module is loaded more than once, in
|
|
||||||
# which case, the credentials are already populated
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_username(cred_name):
|
|
||||||
"""Get the username."""
|
|
||||||
credential = cdb.get_credential_name(cred_name)
|
|
||||||
return credential[const.CREDENTIAL_USERNAME]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_password(cred_name):
|
|
||||||
"""Get the password."""
|
|
||||||
credential = cdb.get_credential_name(cred_name)
|
|
||||||
return credential[const.CREDENTIAL_PASSWORD]
|
|
|
@ -1,236 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
"""Exceptions used by the Cisco plugin."""
|
|
||||||
|
|
||||||
from neutron.common import exceptions
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkSegmentIDNotFound(exceptions.NeutronException):
|
|
||||||
"""Segmentation ID for network is not found."""
|
|
||||||
message = _("Segmentation ID for network %(net_id)s is not found.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoMoreNics(exceptions.NeutronException):
|
|
||||||
"""No more dynamic NICs are available in the system."""
|
|
||||||
message = _("Unable to complete operation. No more dynamic NICs are "
|
|
||||||
"available in the system.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkVlanBindingAlreadyExists(exceptions.NeutronException):
|
|
||||||
"""Binding cannot be created, since it already exists."""
|
|
||||||
message = _("NetworkVlanBinding for %(vlan_id)s and network "
|
|
||||||
"%(network_id)s already exists.")
|
|
||||||
|
|
||||||
|
|
||||||
class VlanIDNotFound(exceptions.NeutronException):
|
|
||||||
"""VLAN ID cannot be found."""
|
|
||||||
message = _("Vlan ID %(vlan_id)s not found.")
|
|
||||||
|
|
||||||
|
|
||||||
class VlanIDOutsidePool(exceptions.NeutronException):
|
|
||||||
"""VLAN ID cannot be allocated, since it is outside the configured pool."""
|
|
||||||
message = _("Unable to complete operation. VLAN ID exists outside of the "
|
|
||||||
"configured network segment range.")
|
|
||||||
|
|
||||||
|
|
||||||
class VlanIDNotAvailable(exceptions.NeutronException):
|
|
||||||
"""No VLAN ID available."""
|
|
||||||
message = _("No Vlan ID available.")
|
|
||||||
|
|
||||||
|
|
||||||
class QosNotFound(exceptions.NeutronException):
|
|
||||||
"""QoS level with this ID cannot be found."""
|
|
||||||
message = _("QoS level %(qos_id)s could not be found "
|
|
||||||
"for tenant %(tenant_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class QosNameAlreadyExists(exceptions.NeutronException):
|
|
||||||
"""QoS Name already exists."""
|
|
||||||
message = _("QoS level with name %(qos_name)s already exists "
|
|
||||||
"for tenant %(tenant_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialNotFound(exceptions.NeutronException):
|
|
||||||
"""Credential with this ID cannot be found."""
|
|
||||||
message = _("Credential %(credential_id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialNameNotFound(exceptions.NeutronException):
|
|
||||||
"""Credential Name could not be found."""
|
|
||||||
message = _("Credential %(credential_name)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialAlreadyExists(exceptions.NeutronException):
|
|
||||||
"""Credential already exists."""
|
|
||||||
message = _("Credential %(credential_name)s already exists.")
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkExists(exceptions.NeutronException):
|
|
||||||
"""Provider network already exists."""
|
|
||||||
message = _("Provider network %s already exists")
|
|
||||||
|
|
||||||
|
|
||||||
class NexusComputeHostNotConfigured(exceptions.NeutronException):
|
|
||||||
"""Connection to compute host is not configured."""
|
|
||||||
message = _("Connection to %(host)s is not configured.")
|
|
||||||
|
|
||||||
|
|
||||||
class NexusConnectFailed(exceptions.NeutronException):
|
|
||||||
"""Failed to connect to Nexus switch."""
|
|
||||||
message = _("Unable to connect to Nexus %(nexus_host)s. Reason: %(exc)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class NexusConfigFailed(exceptions.NeutronException):
|
|
||||||
"""Failed to configure Nexus switch."""
|
|
||||||
message = _("Failed to configure Nexus: %(config)s. Reason: %(exc)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class NexusPortBindingNotFound(exceptions.NeutronException):
|
|
||||||
"""NexusPort Binding is not present."""
|
|
||||||
message = _("Nexus Port Binding (%(filters)s) is not present.")
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
filters = ','.join('%s=%s' % i for i in kwargs.items())
|
|
||||||
super(NexusPortBindingNotFound, self).__init__(filters=filters)
|
|
||||||
|
|
||||||
|
|
||||||
class NoNexusSviSwitch(exceptions.NeutronException):
|
|
||||||
"""No usable nexus switch found."""
|
|
||||||
message = _("No usable Nexus switch found to create SVI interface.")
|
|
||||||
|
|
||||||
|
|
||||||
class PortVnicBindingAlreadyExists(exceptions.NeutronException):
|
|
||||||
"""PortVnic Binding already exists."""
|
|
||||||
message = _("PortVnic Binding %(port_id)s already exists.")
|
|
||||||
|
|
||||||
|
|
||||||
class PortVnicNotFound(exceptions.NeutronException):
|
|
||||||
"""PortVnic Binding is not present."""
|
|
||||||
message = _("PortVnic Binding %(port_id)s is not present.")
|
|
||||||
|
|
||||||
|
|
||||||
class SubnetNotSpecified(exceptions.NeutronException):
|
|
||||||
"""Subnet id not specified."""
|
|
||||||
message = _("No subnet_id specified for router gateway.")
|
|
||||||
|
|
||||||
|
|
||||||
class SubnetInterfacePresent(exceptions.NeutronException):
|
|
||||||
"""Subnet SVI interface already exists."""
|
|
||||||
message = _("Subnet %(subnet_id)s has an interface on %(router_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class PortIdForNexusSvi(exceptions.NeutronException):
|
|
||||||
"""Port Id specified for Nexus SVI."""
|
|
||||||
message = _('Nexus hardware router gateway only uses Subnet Ids.')
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidDetach(exceptions.NeutronException):
|
|
||||||
message = _("Unable to unplug the attachment %(att_id)s from port "
|
|
||||||
"%(port_id)s for network %(net_id)s. The attachment "
|
|
||||||
"%(att_id)s does not exist.")
|
|
||||||
|
|
||||||
|
|
||||||
class PolicyProfileAlreadyExists(exceptions.NeutronException):
|
|
||||||
"""Policy Profile cannot be created since it already exists."""
|
|
||||||
message = _("Policy Profile %(profile_id)s "
|
|
||||||
"already exists.")
|
|
||||||
|
|
||||||
|
|
||||||
class PolicyProfileIdNotFound(exceptions.NotFound):
|
|
||||||
"""Policy Profile with the given UUID cannot be found."""
|
|
||||||
message = _("Policy Profile %(profile_id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkProfileAlreadyExists(exceptions.NeutronException):
|
|
||||||
"""Network Profile cannot be created since it already exists."""
|
|
||||||
message = _("Network Profile %(profile_id)s "
|
|
||||||
"already exists.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkProfileNotFound(exceptions.NotFound):
|
|
||||||
"""Network Profile with the given UUID/name cannot be found."""
|
|
||||||
message = _("Network Profile %(profile)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkProfileInUse(exceptions.InUse):
|
|
||||||
"""Network Profile with the given UUID is in use."""
|
|
||||||
message = _("One or more network segments belonging to network "
|
|
||||||
"profile %(profile)s is in use.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoMoreNetworkSegments(exceptions.NoNetworkAvailable):
|
|
||||||
"""Network segments exhausted for the given network profile."""
|
|
||||||
message = _("No more segments available in network segment pool "
|
|
||||||
"%(network_profile_name)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class VMNetworkNotFound(exceptions.NotFound):
|
|
||||||
"""VM Network with the given name cannot be found."""
|
|
||||||
message = _("VM Network %(name)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class VxlanIDInUse(exceptions.InUse):
|
|
||||||
"""VXLAN ID is in use."""
|
|
||||||
message = _("Unable to create the network. "
|
|
||||||
"The VXLAN ID %(vxlan_id)s is in use.")
|
|
||||||
|
|
||||||
|
|
||||||
class VxlanIDNotFound(exceptions.NotFound):
|
|
||||||
"""VXLAN ID cannot be found."""
|
|
||||||
message = _("Vxlan ID %(vxlan_id)s not found.")
|
|
||||||
|
|
||||||
|
|
||||||
class VxlanIDOutsidePool(exceptions.NeutronException):
|
|
||||||
"""VXLAN ID cannot be allocated, as it is outside the configured pool."""
|
|
||||||
message = _("Unable to complete operation. VXLAN ID exists outside of the "
|
|
||||||
"configured network segment range.")
|
|
||||||
|
|
||||||
|
|
||||||
class VSMConnectionFailed(exceptions.ServiceUnavailable):
|
|
||||||
"""Connection to VSM failed."""
|
|
||||||
message = _("Connection to VSM failed: %(reason)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class VSMError(exceptions.NeutronException):
|
|
||||||
"""Error has occurred on the VSM."""
|
|
||||||
message = _("Internal VSM Error: %(reason)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkBindingNotFound(exceptions.NotFound):
|
|
||||||
"""Network Binding for network cannot be found."""
|
|
||||||
message = _("Network Binding for network %(network_id)s could "
|
|
||||||
"not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class PortBindingNotFound(exceptions.NotFound):
|
|
||||||
"""Port Binding for port cannot be found."""
|
|
||||||
message = _("Port Binding for port %(port_id)s could "
|
|
||||||
"not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileTenantBindingNotFound(exceptions.NotFound):
|
|
||||||
"""Profile to Tenant binding for given profile ID cannot be found."""
|
|
||||||
message = _("Profile-Tenant binding for profile %(profile_id)s could "
|
|
||||||
"not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoClusterFound(exceptions.NotFound):
|
|
||||||
"""No service cluster found to perform multi-segment bridging."""
|
|
||||||
message = _("No service cluster found to perform multi-segment bridging.")
|
|
|
@ -1,138 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ying Liu, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import webob.dec
|
|
||||||
|
|
||||||
from neutron import wsgi
|
|
||||||
|
|
||||||
|
|
||||||
class Fault(webob.exc.HTTPException):
|
|
||||||
"""Error codes for API faults."""
|
|
||||||
|
|
||||||
_fault_names = {
|
|
||||||
400: "malformedRequest",
|
|
||||||
401: "unauthorized",
|
|
||||||
451: "CredentialNotFound",
|
|
||||||
452: "QoSNotFound",
|
|
||||||
453: "NovatenantNotFound",
|
|
||||||
454: "MultiportNotFound",
|
|
||||||
470: "serviceUnavailable",
|
|
||||||
471: "pluginFault"
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, exception):
|
|
||||||
"""Create a Fault for the given webob.exc.exception."""
|
|
||||||
self.wrapped_exc = exception
|
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
|
||||||
def __call__(self, req):
|
|
||||||
"""Generate a WSGI response.
|
|
||||||
|
|
||||||
Response is generated based on the exception passed to constructor.
|
|
||||||
"""
|
|
||||||
# Replace the body with fault details.
|
|
||||||
code = self.wrapped_exc.status_int
|
|
||||||
fault_name = self._fault_names.get(code, "neutronServiceFault")
|
|
||||||
fault_data = {
|
|
||||||
fault_name: {
|
|
||||||
'code': code,
|
|
||||||
'message': self.wrapped_exc.explanation}}
|
|
||||||
# 'code' is an attribute on the fault tag itself
|
|
||||||
content_type = req.best_match_content_type()
|
|
||||||
self.wrapped_exc.body = wsgi.Serializer().serialize(
|
|
||||||
fault_data, content_type)
|
|
||||||
self.wrapped_exc.content_type = content_type
|
|
||||||
return self.wrapped_exc
|
|
||||||
|
|
||||||
|
|
||||||
class PortNotFound(webob.exc.HTTPClientError):
|
|
||||||
"""PortNotFound exception.
|
|
||||||
|
|
||||||
subclass of :class:`~HTTPClientError`
|
|
||||||
|
|
||||||
This indicates that the server did not find the port specified
|
|
||||||
in the HTTP request for a given network
|
|
||||||
|
|
||||||
code: 430, title: Port not Found
|
|
||||||
"""
|
|
||||||
code = 430
|
|
||||||
title = _('Port not Found')
|
|
||||||
explanation = _('Unable to find a port with the specified identifier.')
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialNotFound(webob.exc.HTTPClientError):
|
|
||||||
"""CredentialNotFound exception.
|
|
||||||
|
|
||||||
subclass of :class:`~HTTPClientError`
|
|
||||||
|
|
||||||
This indicates that the server did not find the Credential specified
|
|
||||||
in the HTTP request
|
|
||||||
|
|
||||||
code: 451, title: Credential not Found
|
|
||||||
"""
|
|
||||||
code = 451
|
|
||||||
title = _('Credential Not Found')
|
|
||||||
explanation = _('Unable to find a Credential with'
|
|
||||||
' the specified identifier.')
|
|
||||||
|
|
||||||
|
|
||||||
class QosNotFound(webob.exc.HTTPClientError):
|
|
||||||
"""QosNotFound exception.
|
|
||||||
|
|
||||||
subclass of :class:`~HTTPClientError`
|
|
||||||
|
|
||||||
This indicates that the server did not find the QoS specified
|
|
||||||
in the HTTP request
|
|
||||||
|
|
||||||
code: 452, title: QoS not Found
|
|
||||||
"""
|
|
||||||
code = 452
|
|
||||||
title = _('QoS Not Found')
|
|
||||||
explanation = _('Unable to find a QoS with'
|
|
||||||
' the specified identifier.')
|
|
||||||
|
|
||||||
|
|
||||||
class NovatenantNotFound(webob.exc.HTTPClientError):
|
|
||||||
"""NovatenantNotFound exception.
|
|
||||||
|
|
||||||
subclass of :class:`~HTTPClientError`
|
|
||||||
|
|
||||||
This indicates that the server did not find the Novatenant specified
|
|
||||||
in the HTTP request
|
|
||||||
|
|
||||||
code: 453, title: Nova tenant not Found
|
|
||||||
"""
|
|
||||||
code = 453
|
|
||||||
title = _('Nova tenant Not Found')
|
|
||||||
explanation = _('Unable to find a Novatenant with'
|
|
||||||
' the specified identifier.')
|
|
||||||
|
|
||||||
|
|
||||||
class RequestedStateInvalid(webob.exc.HTTPClientError):
|
|
||||||
"""RequestedStateInvalid exception.
|
|
||||||
|
|
||||||
subclass of :class:`~HTTPClientError`
|
|
||||||
|
|
||||||
This indicates that the server could not update the port state to
|
|
||||||
to the request value
|
|
||||||
|
|
||||||
code: 431, title: Requested State Invalid
|
|
||||||
"""
|
|
||||||
code = 431
|
|
||||||
title = _('Requested State Invalid')
|
|
||||||
explanation = _('Unable to update port state with specified value.')
|
|
|
@ -1,151 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.agent.common import config
|
|
||||||
|
|
||||||
|
|
||||||
cisco_plugins_opts = [
|
|
||||||
cfg.StrOpt('vswitch_plugin',
|
|
||||||
default='neutron.plugins.openvswitch.ovs_neutron_plugin.'
|
|
||||||
'OVSNeutronPluginV2',
|
|
||||||
help=_("Virtual Switch to use")),
|
|
||||||
cfg.StrOpt('nexus_plugin',
|
|
||||||
default='neutron.plugins.cisco.nexus.cisco_nexus_plugin_v2.'
|
|
||||||
'NexusPlugin',
|
|
||||||
help=_("Nexus Switch to use")),
|
|
||||||
]
|
|
||||||
|
|
||||||
cisco_opts = [
|
|
||||||
cfg.StrOpt('vlan_name_prefix', default='q-',
|
|
||||||
help=_("VLAN Name prefix")),
|
|
||||||
cfg.StrOpt('provider_vlan_name_prefix', default='p-',
|
|
||||||
help=_("VLAN Name prefix for provider vlans")),
|
|
||||||
cfg.BoolOpt('provider_vlan_auto_create', default=True,
|
|
||||||
help=_('Provider VLANs are automatically created as needed '
|
|
||||||
'on the Nexus switch')),
|
|
||||||
cfg.BoolOpt('provider_vlan_auto_trunk', default=True,
|
|
||||||
help=_('Provider VLANs are automatically trunked as needed '
|
|
||||||
'on the ports of the Nexus switch')),
|
|
||||||
cfg.BoolOpt('nexus_l3_enable', default=False,
|
|
||||||
help=_("Enable L3 support on the Nexus switches")),
|
|
||||||
cfg.BoolOpt('svi_round_robin', default=False,
|
|
||||||
help=_("Distribute SVI interfaces over all switches")),
|
|
||||||
cfg.StrOpt('model_class',
|
|
||||||
default='neutron.plugins.cisco.models.virt_phy_sw_v2.'
|
|
||||||
'VirtualPhysicalSwitchModelV2',
|
|
||||||
help=_("Model Class")),
|
|
||||||
cfg.StrOpt('nexus_driver',
|
|
||||||
default='neutron.plugins.cisco.test.nexus.'
|
|
||||||
'fake_nexus_driver.CiscoNEXUSFakeDriver',
|
|
||||||
help=_("Nexus Driver Name")),
|
|
||||||
]
|
|
||||||
|
|
||||||
cisco_n1k_opts = [
|
|
||||||
cfg.StrOpt('integration_bridge', default='br-int',
|
|
||||||
help=_("N1K Integration Bridge")),
|
|
||||||
cfg.BoolOpt('enable_tunneling', default=True,
|
|
||||||
help=_("N1K Enable Tunneling")),
|
|
||||||
cfg.StrOpt('tunnel_bridge', default='br-tun',
|
|
||||||
help=_("N1K Tunnel Bridge")),
|
|
||||||
cfg.StrOpt('local_ip', default='10.0.0.3',
|
|
||||||
help=_("N1K Local IP")),
|
|
||||||
cfg.StrOpt('tenant_network_type', default='local',
|
|
||||||
help=_("N1K Tenant Network Type")),
|
|
||||||
cfg.StrOpt('bridge_mappings', default='',
|
|
||||||
help=_("N1K Bridge Mappings")),
|
|
||||||
cfg.StrOpt('vxlan_id_ranges', default='5000:10000',
|
|
||||||
help=_("N1K VXLAN ID Ranges")),
|
|
||||||
cfg.StrOpt('network_vlan_ranges', default='vlan:1:4095',
|
|
||||||
help=_("N1K Network VLAN Ranges")),
|
|
||||||
cfg.StrOpt('default_network_profile', default='default_network_profile',
|
|
||||||
help=_("N1K default network profile")),
|
|
||||||
cfg.StrOpt('default_policy_profile', default='service_profile',
|
|
||||||
help=_("N1K default policy profile")),
|
|
||||||
cfg.StrOpt('network_node_policy_profile', default='dhcp_pp',
|
|
||||||
help=_("N1K policy profile for network node")),
|
|
||||||
cfg.IntOpt('poll_duration', default=10,
|
|
||||||
help=_("N1K Policy profile polling duration in seconds")),
|
|
||||||
cfg.IntOpt('http_pool_size', default=4,
|
|
||||||
help=_("Number of threads to use to make HTTP requests")),
|
|
||||||
]
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(cisco_opts, "CISCO")
|
|
||||||
cfg.CONF.register_opts(cisco_n1k_opts, "CISCO_N1K")
|
|
||||||
cfg.CONF.register_opts(cisco_plugins_opts, "CISCO_PLUGINS")
|
|
||||||
config.register_root_helper(cfg.CONF)
|
|
||||||
|
|
||||||
# shortcuts
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CISCO = cfg.CONF.CISCO
|
|
||||||
CISCO_N1K = cfg.CONF.CISCO_N1K
|
|
||||||
CISCO_PLUGINS = cfg.CONF.CISCO_PLUGINS
|
|
||||||
|
|
||||||
#
|
|
||||||
# device_dictionary - Contains all external device configuration.
|
|
||||||
#
|
|
||||||
# When populated the device dictionary format is:
|
|
||||||
# {('<device ID>', '<device ipaddr>', '<keyword>'): '<value>', ...}
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# {('NEXUS_SWITCH', '1.1.1.1', 'username'): 'admin',
|
|
||||||
# ('NEXUS_SWITCH', '1.1.1.1', 'password'): 'mySecretPassword',
|
|
||||||
# ('NEXUS_SWITCH', '1.1.1.1', 'compute1'): '1/1', ...}
|
|
||||||
#
|
|
||||||
device_dictionary = {}
|
|
||||||
|
|
||||||
#
|
|
||||||
# first_device_ip - IP address of first switch discovered in config
|
|
||||||
#
|
|
||||||
# Used for SVI placement when round-robin placement is disabled
|
|
||||||
#
|
|
||||||
first_device_ip = None
|
|
||||||
|
|
||||||
|
|
||||||
class CiscoConfigOptions():
|
|
||||||
"""Cisco Configuration Options Class."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._create_device_dictionary()
|
|
||||||
|
|
||||||
def _create_device_dictionary(self):
|
|
||||||
"""
|
|
||||||
Create the device dictionary from the cisco_plugins.ini
|
|
||||||
device supported sections. Ex. NEXUS_SWITCH, N1KV.
|
|
||||||
"""
|
|
||||||
|
|
||||||
global first_device_ip
|
|
||||||
|
|
||||||
multi_parser = cfg.MultiConfigParser()
|
|
||||||
read_ok = multi_parser.read(CONF.config_file)
|
|
||||||
|
|
||||||
if len(read_ok) != len(CONF.config_file):
|
|
||||||
raise cfg.Error(_("Some config files were not parsed properly"))
|
|
||||||
|
|
||||||
first_device_ip = None
|
|
||||||
for parsed_file in multi_parser.parsed:
|
|
||||||
for parsed_item in parsed_file.keys():
|
|
||||||
dev_id, sep, dev_ip = parsed_item.partition(':')
|
|
||||||
if dev_id.lower() in ['nexus_switch', 'n1kv']:
|
|
||||||
for dev_key, value in parsed_file[parsed_item].items():
|
|
||||||
if dev_ip and not first_device_ip:
|
|
||||||
first_device_ip = dev_ip
|
|
||||||
device_dictionary[dev_id, dev_ip, dev_key] = value[0]
|
|
||||||
|
|
||||||
|
|
||||||
def get_device_dictionary():
|
|
||||||
return device_dictionary
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
#
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,185 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Abhishek Raut, Cisco Systems Inc.
|
|
||||||
# @author: Rudrajit Tapadar, Cisco Systems Inc.
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from neutron.db import model_base
|
|
||||||
from neutron.db import models_v2
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class N1kvVlanAllocation(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents allocation state of vlan_id on physical network."""
|
|
||||||
__tablename__ = 'cisco_n1kv_vlan_allocations'
|
|
||||||
|
|
||||||
physical_network = sa.Column(sa.String(64),
|
|
||||||
nullable=False,
|
|
||||||
primary_key=True)
|
|
||||||
vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
|
|
||||||
autoincrement=False)
|
|
||||||
allocated = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
||||||
network_profile_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('cisco_network_profiles.id',
|
|
||||||
ondelete="CASCADE"),
|
|
||||||
nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
class N1kvVxlanAllocation(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents allocation state of vxlan_id."""
|
|
||||||
__tablename__ = 'cisco_n1kv_vxlan_allocations'
|
|
||||||
|
|
||||||
vxlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
|
|
||||||
autoincrement=False)
|
|
||||||
allocated = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
||||||
network_profile_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('cisco_network_profiles.id',
|
|
||||||
ondelete="CASCADE"),
|
|
||||||
nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
class N1kvPortBinding(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents binding of ports to policy profile."""
|
|
||||||
__tablename__ = 'cisco_n1kv_port_bindings'
|
|
||||||
|
|
||||||
port_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('ports.id', ondelete="CASCADE"),
|
|
||||||
primary_key=True)
|
|
||||||
profile_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('cisco_policy_profiles.id'))
|
|
||||||
|
|
||||||
|
|
||||||
class N1kvNetworkBinding(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents binding of virtual network to physical realization."""
|
|
||||||
__tablename__ = 'cisco_n1kv_network_bindings'
|
|
||||||
|
|
||||||
network_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('networks.id', ondelete="CASCADE"),
|
|
||||||
primary_key=True)
|
|
||||||
network_type = sa.Column(sa.String(32), nullable=False)
|
|
||||||
physical_network = sa.Column(sa.String(64))
|
|
||||||
segmentation_id = sa.Column(sa.Integer)
|
|
||||||
multicast_ip = sa.Column(sa.String(32))
|
|
||||||
profile_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('cisco_network_profiles.id'))
|
|
||||||
|
|
||||||
|
|
||||||
class N1kVmNetwork(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents VM Network information."""
|
|
||||||
__tablename__ = 'cisco_n1kv_vmnetworks'
|
|
||||||
|
|
||||||
name = sa.Column(sa.String(80), primary_key=True)
|
|
||||||
profile_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('cisco_policy_profiles.id'))
|
|
||||||
network_id = sa.Column(sa.String(36))
|
|
||||||
port_count = sa.Column(sa.Integer)
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkProfile(model_base.BASEV2, models_v2.HasId):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Nexus1000V Network Profiles
|
|
||||||
|
|
||||||
segment_type - VLAN, OVERLAY, TRUNK, MULTI_SEGMENT
|
|
||||||
sub_type - TRUNK_VLAN, TRUNK_VXLAN, native_vxlan, enhanced_vxlan
|
|
||||||
segment_range - '<integer>-<integer>'
|
|
||||||
multicast_ip_index - <integer>
|
|
||||||
multicast_ip_range - '<ip>-<ip>'
|
|
||||||
physical_network - Name for the physical network
|
|
||||||
"""
|
|
||||||
__tablename__ = 'cisco_network_profiles'
|
|
||||||
|
|
||||||
name = sa.Column(sa.String(255))
|
|
||||||
segment_type = sa.Column(sa.Enum(cisco_constants.NETWORK_TYPE_VLAN,
|
|
||||||
cisco_constants.NETWORK_TYPE_OVERLAY,
|
|
||||||
cisco_constants.NETWORK_TYPE_TRUNK,
|
|
||||||
cisco_constants.
|
|
||||||
NETWORK_TYPE_MULTI_SEGMENT,
|
|
||||||
name='segment_type'),
|
|
||||||
nullable=False)
|
|
||||||
sub_type = sa.Column(sa.String(255))
|
|
||||||
segment_range = sa.Column(sa.String(255))
|
|
||||||
multicast_ip_index = sa.Column(sa.Integer, default=0)
|
|
||||||
multicast_ip_range = sa.Column(sa.String(255))
|
|
||||||
physical_network = sa.Column(sa.String(255))
|
|
||||||
|
|
||||||
|
|
||||||
class PolicyProfile(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Nexus1000V Network Profiles
|
|
||||||
|
|
||||||
Both 'id' and 'name' are coming from Nexus1000V switch
|
|
||||||
"""
|
|
||||||
__tablename__ = 'cisco_policy_profiles'
|
|
||||||
|
|
||||||
id = sa.Column(sa.String(36), primary_key=True)
|
|
||||||
name = sa.Column(sa.String(255))
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileBinding(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Represents a binding of Network Profile
|
|
||||||
or Policy Profile to tenant_id
|
|
||||||
"""
|
|
||||||
__tablename__ = 'cisco_n1kv_profile_bindings'
|
|
||||||
|
|
||||||
profile_type = sa.Column(sa.Enum(cisco_constants.NETWORK,
|
|
||||||
cisco_constants.POLICY,
|
|
||||||
name='profile_type'))
|
|
||||||
tenant_id = sa.Column(sa.String(36),
|
|
||||||
primary_key=True,
|
|
||||||
default=cisco_constants.TENANT_ID_NOT_SET)
|
|
||||||
profile_id = sa.Column(sa.String(36), primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
class N1kvTrunkSegmentBinding(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents binding of segments in trunk networks."""
|
|
||||||
__tablename__ = 'cisco_n1kv_trunk_segments'
|
|
||||||
|
|
||||||
trunk_segment_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('networks.id',
|
|
||||||
ondelete="CASCADE"),
|
|
||||||
primary_key=True)
|
|
||||||
segment_id = sa.Column(sa.String(36), nullable=False, primary_key=True)
|
|
||||||
dot1qtag = sa.Column(sa.String(36), nullable=False, primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
class N1kvMultiSegmentNetworkBinding(model_base.BASEV2):
|
|
||||||
|
|
||||||
"""Represents binding of segments in multi-segment networks."""
|
|
||||||
__tablename__ = 'cisco_n1kv_multi_segments'
|
|
||||||
|
|
||||||
multi_segment_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('networks.id',
|
|
||||||
ondelete="CASCADE"),
|
|
||||||
primary_key=True)
|
|
||||||
segment1_id = sa.Column(sa.String(36), nullable=False, primary_key=True)
|
|
||||||
segment2_id = sa.Column(sa.String(36), nullable=False, primary_key=True)
|
|
||||||
encap_profile_name = sa.Column(sa.String(36))
|
|
|
@ -1,290 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012, Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
from sqlalchemy.orm import exc
|
|
||||||
|
|
||||||
from neutron.db import api as db
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.openstack.common import uuidutils
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants as const
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as c_exc
|
|
||||||
from neutron.plugins.cisco.db import network_models_v2
|
|
||||||
# Do NOT remove this import. It is required for all the models to be seen
|
|
||||||
# by db.initialize() when called from VirtualPhysicalSwitchModelV2.__init__.
|
|
||||||
from neutron.plugins.cisco.db import nexus_models_v2 # noqa
|
|
||||||
from neutron.plugins.openvswitch import ovs_models_v2
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_qoss(tenant_id):
|
|
||||||
"""Lists all the qos to tenant associations."""
|
|
||||||
LOG.debug(_("get_all_qoss() called"))
|
|
||||||
session = db.get_session()
|
|
||||||
return (session.query(network_models_v2.QoS).
|
|
||||||
filter_by(tenant_id=tenant_id).all())
|
|
||||||
|
|
||||||
|
|
||||||
def get_qos(tenant_id, qos_id):
|
|
||||||
"""Lists the qos given a tenant_id and qos_id."""
|
|
||||||
LOG.debug(_("get_qos() called"))
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
return (session.query(network_models_v2.QoS).
|
|
||||||
filter_by(tenant_id=tenant_id).
|
|
||||||
filter_by(qos_id=qos_id).one())
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise c_exc.QosNotFound(qos_id=qos_id,
|
|
||||||
tenant_id=tenant_id)
|
|
||||||
|
|
||||||
|
|
||||||
def add_qos(tenant_id, qos_name, qos_desc):
|
|
||||||
"""Adds a qos to tenant association."""
|
|
||||||
LOG.debug(_("add_qos() called"))
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
qos = (session.query(network_models_v2.QoS).
|
|
||||||
filter_by(tenant_id=tenant_id).
|
|
||||||
filter_by(qos_name=qos_name).one())
|
|
||||||
raise c_exc.QosNameAlreadyExists(qos_name=qos_name,
|
|
||||||
tenant_id=tenant_id)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
qos = network_models_v2.QoS(qos_id=uuidutils.generate_uuid(),
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
qos_name=qos_name,
|
|
||||||
qos_desc=qos_desc)
|
|
||||||
session.add(qos)
|
|
||||||
session.flush()
|
|
||||||
return qos
|
|
||||||
|
|
||||||
|
|
||||||
def remove_qos(tenant_id, qos_id):
|
|
||||||
"""Removes a qos to tenant association."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
qos = (session.query(network_models_v2.QoS).
|
|
||||||
filter_by(tenant_id=tenant_id).
|
|
||||||
filter_by(qos_id=qos_id).one())
|
|
||||||
session.delete(qos)
|
|
||||||
session.flush()
|
|
||||||
return qos
|
|
||||||
except exc.NoResultFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def update_qos(tenant_id, qos_id, new_qos_name=None):
|
|
||||||
"""Updates a qos to tenant association."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
qos = (session.query(network_models_v2.QoS).
|
|
||||||
filter_by(tenant_id=tenant_id).
|
|
||||||
filter_by(qos_id=qos_id).one())
|
|
||||||
if new_qos_name:
|
|
||||||
qos["qos_name"] = new_qos_name
|
|
||||||
session.merge(qos)
|
|
||||||
session.flush()
|
|
||||||
return qos
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise c_exc.QosNotFound(qos_id=qos_id,
|
|
||||||
tenant_id=tenant_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_credentials():
|
|
||||||
"""Lists all the creds for a tenant."""
|
|
||||||
session = db.get_session()
|
|
||||||
return (session.query(network_models_v2.Credential).all())
|
|
||||||
|
|
||||||
|
|
||||||
def get_credential(credential_id):
|
|
||||||
"""Lists the creds for given a cred_id."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
return (session.query(network_models_v2.Credential).
|
|
||||||
filter_by(credential_id=credential_id).one())
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise c_exc.CredentialNotFound(credential_id=credential_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_credential_name(credential_name):
|
|
||||||
"""Lists the creds for given a cred_name."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
return (session.query(network_models_v2.Credential).
|
|
||||||
filter_by(credential_name=credential_name).one())
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise c_exc.CredentialNameNotFound(credential_name=credential_name)
|
|
||||||
|
|
||||||
|
|
||||||
def add_credential(credential_name, user_name, password, type):
|
|
||||||
"""Create a credential."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
cred = (session.query(network_models_v2.Credential).
|
|
||||||
filter_by(credential_name=credential_name).one())
|
|
||||||
raise c_exc.CredentialAlreadyExists(credential_name=credential_name)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
cred = network_models_v2.Credential(
|
|
||||||
credential_id=uuidutils.generate_uuid(),
|
|
||||||
credential_name=credential_name,
|
|
||||||
user_name=user_name,
|
|
||||||
password=password,
|
|
||||||
type=type)
|
|
||||||
session.add(cred)
|
|
||||||
session.flush()
|
|
||||||
return cred
|
|
||||||
|
|
||||||
|
|
||||||
def remove_credential(credential_id):
|
|
||||||
"""Removes a credential."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
cred = (session.query(network_models_v2.Credential).
|
|
||||||
filter_by(credential_id=credential_id).one())
|
|
||||||
session.delete(cred)
|
|
||||||
session.flush()
|
|
||||||
return cred
|
|
||||||
except exc.NoResultFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def update_credential(credential_id,
|
|
||||||
new_user_name=None, new_password=None):
|
|
||||||
"""Updates a credential for a tenant."""
|
|
||||||
session = db.get_session()
|
|
||||||
try:
|
|
||||||
cred = (session.query(network_models_v2.Credential).
|
|
||||||
filter_by(credential_id=credential_id).one())
|
|
||||||
if new_user_name:
|
|
||||||
cred["user_name"] = new_user_name
|
|
||||||
if new_password:
|
|
||||||
cred["password"] = new_password
|
|
||||||
session.merge(cred)
|
|
||||||
session.flush()
|
|
||||||
return cred
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise c_exc.CredentialNotFound(credential_id=credential_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_n1kv_credentials():
|
|
||||||
session = db.get_session()
|
|
||||||
return (session.query(network_models_v2.Credential).
|
|
||||||
filter_by(type='n1kv'))
|
|
||||||
|
|
||||||
|
|
||||||
def add_provider_network(network_id, network_type, segmentation_id):
|
|
||||||
"""Add a network to the provider network table."""
|
|
||||||
session = db.get_session()
|
|
||||||
if session.query(network_models_v2.ProviderNetwork).filter_by(
|
|
||||||
network_id=network_id).first():
|
|
||||||
raise c_exc.ProviderNetworkExists(network_id)
|
|
||||||
pnet = network_models_v2.ProviderNetwork(network_id=network_id,
|
|
||||||
network_type=network_type,
|
|
||||||
segmentation_id=segmentation_id)
|
|
||||||
session.add(pnet)
|
|
||||||
session.flush()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_provider_network(network_id):
|
|
||||||
"""Remove network_id from the provider network table.
|
|
||||||
|
|
||||||
:param network_id: Any network id. If it is not in the table, do nothing.
|
|
||||||
:return: network_id if it was in the table and successfully removed.
|
|
||||||
"""
|
|
||||||
session = db.get_session()
|
|
||||||
pnet = (session.query(network_models_v2.ProviderNetwork).
|
|
||||||
filter_by(network_id=network_id).first())
|
|
||||||
if pnet:
|
|
||||||
session.delete(pnet)
|
|
||||||
session.flush()
|
|
||||||
return network_id
|
|
||||||
|
|
||||||
|
|
||||||
def is_provider_network(network_id):
|
|
||||||
"""Return True if network_id is in the provider network table."""
|
|
||||||
session = db.get_session()
|
|
||||||
if session.query(network_models_v2.ProviderNetwork).filter_by(
|
|
||||||
network_id=network_id).first():
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def is_provider_vlan(vlan_id):
|
|
||||||
"""Check for a for a vlan provider network with the specified vland_id.
|
|
||||||
|
|
||||||
Returns True if the provider network table contains a vlan network
|
|
||||||
with the specified vlan_id.
|
|
||||||
"""
|
|
||||||
session = db.get_session()
|
|
||||||
if (session.query(network_models_v2.ProviderNetwork).
|
|
||||||
filter_by(network_type=const.NETWORK_TYPE_VLAN,
|
|
||||||
segmentation_id=vlan_id).first()):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_ovs_vlans():
|
|
||||||
session = db.get_session()
|
|
||||||
bindings = (session.query(ovs_models_v2.VlanAllocation.vlan_id).
|
|
||||||
filter_by(allocated=True))
|
|
||||||
return [binding.vlan_id for binding in bindings]
|
|
||||||
|
|
||||||
|
|
||||||
class Credential_db_mixin(object):
|
|
||||||
|
|
||||||
"""Mixin class for Cisco Credentials as a resource."""
|
|
||||||
|
|
||||||
def _make_credential_dict(self, credential, fields=None):
|
|
||||||
res = {'credential_id': credential['credential_id'],
|
|
||||||
'credential_name': credential['credential_name'],
|
|
||||||
'user_name': credential['user_name'],
|
|
||||||
'password': credential['password'],
|
|
||||||
'type': credential['type']}
|
|
||||||
return self._fields(res, fields)
|
|
||||||
|
|
||||||
def create_credential(self, context, credential):
|
|
||||||
"""Create a credential."""
|
|
||||||
c = credential['credential']
|
|
||||||
cred = add_credential(c['credential_name'],
|
|
||||||
c['user_name'],
|
|
||||||
c['password'],
|
|
||||||
c['type'])
|
|
||||||
return self._make_credential_dict(cred)
|
|
||||||
|
|
||||||
def get_credentials(self, context, filters=None, fields=None):
|
|
||||||
"""Retrieve a list of credentials."""
|
|
||||||
return self._get_collection(context,
|
|
||||||
network_models_v2.Credential,
|
|
||||||
self._make_credential_dict,
|
|
||||||
filters=filters,
|
|
||||||
fields=fields)
|
|
||||||
|
|
||||||
def get_credential(self, context, id, fields=None):
|
|
||||||
"""Retireve the requested credential based on its id."""
|
|
||||||
credential = get_credential(id)
|
|
||||||
return self._make_credential_dict(credential, fields)
|
|
||||||
|
|
||||||
def update_credential(self, context, id, credential):
|
|
||||||
"""Update a credential based on its id."""
|
|
||||||
c = credential['credential']
|
|
||||||
cred = update_credential(id,
|
|
||||||
c['user_name'],
|
|
||||||
c['password'])
|
|
||||||
return self._make_credential_dict(cred)
|
|
||||||
|
|
||||||
def delete_credential(self, context, id):
|
|
||||||
"""Delete a credential based on its id."""
|
|
||||||
return remove_credential(id)
|
|
|
@ -1,56 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012, Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from neutron.db import model_base
|
|
||||||
|
|
||||||
|
|
||||||
class QoS(model_base.BASEV2):
|
|
||||||
"""Represents QoS policies for a tenant."""
|
|
||||||
|
|
||||||
__tablename__ = 'cisco_qos_policies'
|
|
||||||
|
|
||||||
qos_id = sa.Column(sa.String(255))
|
|
||||||
tenant_id = sa.Column(sa.String(255), primary_key=True)
|
|
||||||
qos_name = sa.Column(sa.String(255), primary_key=True)
|
|
||||||
qos_desc = sa.Column(sa.String(255))
|
|
||||||
|
|
||||||
|
|
||||||
class Credential(model_base.BASEV2):
|
|
||||||
"""Represents credentials for a tenant to control Cisco switches."""
|
|
||||||
|
|
||||||
__tablename__ = 'cisco_credentials'
|
|
||||||
|
|
||||||
credential_id = sa.Column(sa.String(255))
|
|
||||||
credential_name = sa.Column(sa.String(255), primary_key=True)
|
|
||||||
user_name = sa.Column(sa.String(255))
|
|
||||||
password = sa.Column(sa.String(255))
|
|
||||||
type = sa.Column(sa.String(255))
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetwork(model_base.BASEV2):
|
|
||||||
"""Represents networks that were created as provider networks."""
|
|
||||||
|
|
||||||
__tablename__ = 'cisco_provider_networks'
|
|
||||||
|
|
||||||
network_id = sa.Column(sa.String(36),
|
|
||||||
sa.ForeignKey('networks.id', ondelete="CASCADE"),
|
|
||||||
primary_key=True)
|
|
||||||
network_type = sa.Column(sa.String(255), nullable=False)
|
|
||||||
segmentation_id = sa.Column(sa.Integer, nullable=False)
|
|
|
@ -1,154 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012, Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
# @author: Arvind Somya, Cisco Systems, Inc. (asomya@cisco.com)
|
|
||||||
#
|
|
||||||
|
|
||||||
import sqlalchemy.orm.exc as sa_exc
|
|
||||||
|
|
||||||
import neutron.db.api as db
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as c_exc
|
|
||||||
from neutron.plugins.cisco.db import nexus_models_v2
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def get_nexusport_binding(port_id, vlan_id, switch_ip, instance_id):
|
|
||||||
"""Lists a nexusport binding."""
|
|
||||||
LOG.debug(_("get_nexusport_binding() called"))
|
|
||||||
return _lookup_all_nexus_bindings(port_id=port_id,
|
|
||||||
vlan_id=vlan_id,
|
|
||||||
switch_ip=switch_ip,
|
|
||||||
instance_id=instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_nexusvlan_binding(vlan_id, switch_ip):
|
|
||||||
"""Lists a vlan and switch binding."""
|
|
||||||
LOG.debug(_("get_nexusvlan_binding() called"))
|
|
||||||
return _lookup_all_nexus_bindings(vlan_id=vlan_id, switch_ip=switch_ip)
|
|
||||||
|
|
||||||
|
|
||||||
def add_nexusport_binding(port_id, vlan_id, switch_ip, instance_id):
|
|
||||||
"""Adds a nexusport binding."""
|
|
||||||
LOG.debug(_("add_nexusport_binding() called"))
|
|
||||||
session = db.get_session()
|
|
||||||
binding = nexus_models_v2.NexusPortBinding(port_id=port_id,
|
|
||||||
vlan_id=vlan_id,
|
|
||||||
switch_ip=switch_ip,
|
|
||||||
instance_id=instance_id)
|
|
||||||
session.add(binding)
|
|
||||||
session.flush()
|
|
||||||
return binding
|
|
||||||
|
|
||||||
|
|
||||||
def remove_nexusport_binding(port_id, vlan_id, switch_ip, instance_id):
|
|
||||||
"""Removes a nexusport binding."""
|
|
||||||
LOG.debug(_("remove_nexusport_binding() called"))
|
|
||||||
session = db.get_session()
|
|
||||||
binding = _lookup_all_nexus_bindings(session=session,
|
|
||||||
vlan_id=vlan_id,
|
|
||||||
switch_ip=switch_ip,
|
|
||||||
port_id=port_id,
|
|
||||||
instance_id=instance_id)
|
|
||||||
for bind in binding:
|
|
||||||
session.delete(bind)
|
|
||||||
session.flush()
|
|
||||||
return binding
|
|
||||||
|
|
||||||
|
|
||||||
def update_nexusport_binding(port_id, new_vlan_id):
|
|
||||||
"""Updates nexusport binding."""
|
|
||||||
if not new_vlan_id:
|
|
||||||
LOG.warning(_("update_nexusport_binding called with no vlan"))
|
|
||||||
return
|
|
||||||
LOG.debug(_("update_nexusport_binding called"))
|
|
||||||
session = db.get_session()
|
|
||||||
binding = _lookup_one_nexus_binding(session=session, port_id=port_id)
|
|
||||||
binding.vlan_id = new_vlan_id
|
|
||||||
session.merge(binding)
|
|
||||||
session.flush()
|
|
||||||
return binding
|
|
||||||
|
|
||||||
|
|
||||||
def get_nexusvm_bindings(vlan_id, instance_id):
|
|
||||||
"""Lists nexusvm bindings."""
|
|
||||||
LOG.debug(_("get_nexusvm_binding() called"))
|
|
||||||
|
|
||||||
return _lookup_all_nexus_bindings(vlan_id=vlan_id,
|
|
||||||
instance_id=instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_vlan_switch_binding(port_id, vlan_id, switch_ip):
|
|
||||||
"""Lists nexusvm bindings."""
|
|
||||||
LOG.debug(_("get_port_vlan_switch_binding() called"))
|
|
||||||
return _lookup_all_nexus_bindings(port_id=port_id,
|
|
||||||
switch_ip=switch_ip,
|
|
||||||
vlan_id=vlan_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_switch_bindings(port_id, switch_ip):
|
|
||||||
"""List all vm/vlan bindings on a Nexus switch port."""
|
|
||||||
LOG.debug(_("get_port_switch_bindings() called, "
|
|
||||||
"port:'%(port_id)s', switch:'%(switch_ip)s'"),
|
|
||||||
{'port_id': port_id, 'switch_ip': switch_ip})
|
|
||||||
try:
|
|
||||||
return _lookup_all_nexus_bindings(port_id=port_id,
|
|
||||||
switch_ip=switch_ip)
|
|
||||||
except c_exc.NexusPortBindingNotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_nexussvi_bindings():
|
|
||||||
"""Lists nexus svi bindings."""
|
|
||||||
LOG.debug(_("get_nexussvi_bindings() called"))
|
|
||||||
return _lookup_all_nexus_bindings(port_id='router')
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_nexus_bindings(query_type, session=None, **bfilter):
|
|
||||||
"""Look up 'query_type' Nexus bindings matching the filter.
|
|
||||||
|
|
||||||
:param query_type: 'all', 'one' or 'first'
|
|
||||||
:param session: db session
|
|
||||||
:param bfilter: filter for bindings query
|
|
||||||
:return: bindings if query gave a result, else
|
|
||||||
raise NexusPortBindingNotFound.
|
|
||||||
"""
|
|
||||||
if session is None:
|
|
||||||
session = db.get_session()
|
|
||||||
query_method = getattr(session.query(
|
|
||||||
nexus_models_v2.NexusPortBinding).filter_by(**bfilter), query_type)
|
|
||||||
try:
|
|
||||||
bindings = query_method()
|
|
||||||
if bindings:
|
|
||||||
return bindings
|
|
||||||
except sa_exc.NoResultFound:
|
|
||||||
pass
|
|
||||||
raise c_exc.NexusPortBindingNotFound(**bfilter)
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_all_nexus_bindings(session=None, **bfilter):
|
|
||||||
return _lookup_nexus_bindings('all', session, **bfilter)
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_one_nexus_binding(session=None, **bfilter):
|
|
||||||
return _lookup_nexus_bindings('one', session, **bfilter)
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_first_nexus_binding(session=None, **bfilter):
|
|
||||||
return _lookup_nexus_bindings('first', session, **bfilter)
|
|
|
@ -1,46 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012, Cisco 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.
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from neutron.db import model_base
|
|
||||||
|
|
||||||
|
|
||||||
class NexusPortBinding(model_base.BASEV2):
|
|
||||||
"""Represents a binding of VM's to nexus ports."""
|
|
||||||
|
|
||||||
__tablename__ = "cisco_nexusport_bindings"
|
|
||||||
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
|
||||||
port_id = sa.Column(sa.String(255))
|
|
||||||
vlan_id = sa.Column(sa.Integer, nullable=False)
|
|
||||||
switch_ip = sa.Column(sa.String(255), nullable=False)
|
|
||||||
instance_id = sa.Column(sa.String(255), nullable=False)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""Just the binding, without the id key."""
|
|
||||||
return ("<NexusPortBinding(%s,%s,%s,%s)>" %
|
|
||||||
(self.port_id, self.vlan_id, self.switch_ip, self.instance_id))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
"""Compare only the binding, without the id key."""
|
|
||||||
return (
|
|
||||||
self.port_id == other.port_id and
|
|
||||||
self.vlan_id == other.vlan_id and
|
|
||||||
self.switch_ip == other.switch_ip and
|
|
||||||
self.instance_id == other.instance_id
|
|
||||||
)
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 OpenStack Foundation.
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
|
@ -1,52 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ying Liu, Cisco Systems, Inc.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
def get_view_builder(req):
|
|
||||||
base_url = req.application_url
|
|
||||||
return ViewBuilder(base_url)
|
|
||||||
|
|
||||||
|
|
||||||
class ViewBuilder(object):
|
|
||||||
"""ViewBuilder for Credential, derived from neutron.views.networks."""
|
|
||||||
|
|
||||||
def __init__(self, base_url):
|
|
||||||
"""Initialize builder.
|
|
||||||
|
|
||||||
:param base_url: url of the root wsgi application
|
|
||||||
"""
|
|
||||||
self.base_url = base_url
|
|
||||||
|
|
||||||
def build(self, credential_data, is_detail=False):
|
|
||||||
"""Generic method used to generate a credential entity."""
|
|
||||||
if is_detail:
|
|
||||||
credential = self._build_detail(credential_data)
|
|
||||||
else:
|
|
||||||
credential = self._build_simple(credential_data)
|
|
||||||
return credential
|
|
||||||
|
|
||||||
def _build_simple(self, credential_data):
|
|
||||||
"""Return a simple description of credential."""
|
|
||||||
return dict(credential=dict(id=credential_data['credential_id']))
|
|
||||||
|
|
||||||
def _build_detail(self, credential_data):
|
|
||||||
"""Return a detailed description of credential."""
|
|
||||||
return dict(credential=dict(id=credential_data['credential_id'],
|
|
||||||
name=credential_data['user_name'],
|
|
||||||
password=credential_data['password']))
|
|
|
@ -1,52 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ying Liu, Cisco Systems, Inc.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
def get_view_builder(req):
|
|
||||||
base_url = req.application_url
|
|
||||||
return ViewBuilder(base_url)
|
|
||||||
|
|
||||||
|
|
||||||
class ViewBuilder(object):
|
|
||||||
"""ViewBuilder for QoS, derived from neutron.views.networks."""
|
|
||||||
|
|
||||||
def __init__(self, base_url):
|
|
||||||
"""Initialize builder.
|
|
||||||
|
|
||||||
:param base_url: url of the root wsgi application
|
|
||||||
"""
|
|
||||||
self.base_url = base_url
|
|
||||||
|
|
||||||
def build(self, qos_data, is_detail=False):
|
|
||||||
"""Generic method used to generate a QoS entity."""
|
|
||||||
if is_detail:
|
|
||||||
qos = self._build_detail(qos_data)
|
|
||||||
else:
|
|
||||||
qos = self._build_simple(qos_data)
|
|
||||||
return qos
|
|
||||||
|
|
||||||
def _build_simple(self, qos_data):
|
|
||||||
"""Return a simple description of qos."""
|
|
||||||
return dict(qos=dict(id=qos_data['qos_id']))
|
|
||||||
|
|
||||||
def _build_detail(self, qos_data):
|
|
||||||
"""Return a detailed description of qos."""
|
|
||||||
return dict(qos=dict(id=qos_data['qos_id'],
|
|
||||||
name=qos_data['qos_name'],
|
|
||||||
description=qos_data['qos_desc']))
|
|
|
@ -1,84 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ying Liu, Cisco Systems, Inc.
|
|
||||||
# @author: Abhishek Raut, Cisco Systems, Inc
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron import manager
|
|
||||||
|
|
||||||
|
|
||||||
# Attribute Map
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'credentials': {
|
|
||||||
'credential_id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'is_visible': True},
|
|
||||||
'credential_name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': False, 'default': ''},
|
|
||||||
'type': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'user_name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'password': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Credential(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
"""Returns Extended Resource Name."""
|
|
||||||
return "Cisco Credential"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
"""Returns Extended Resource Alias."""
|
|
||||||
return "credential"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
"""Returns Extended Resource Description."""
|
|
||||||
return "Credential include username and password"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
"""Returns Extended Resource Namespace."""
|
|
||||||
return "http://docs.ciscocloud.com/api/ext/credential/v2.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
"""Returns Extended Resource Update Time."""
|
|
||||||
return "2011-07-25T13:25:27-06:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
"""Returns Extended Resources."""
|
|
||||||
resource_name = "credential"
|
|
||||||
collection_name = resource_name + "s"
|
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
|
||||||
params = RESOURCE_ATTRIBUTE_MAP.get(collection_name, dict())
|
|
||||||
controller = base.create_resource(collection_name,
|
|
||||||
resource_name,
|
|
||||||
plugin, params)
|
|
||||||
return [extensions.ResourceExtension(collection_name,
|
|
||||||
controller)]
|
|
|
@ -1,106 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Abhishek Raut, Cisco Systems, Inc.
|
|
||||||
# @author: Rudrajit Tapadar, Cisco Systems, Inc.
|
|
||||||
# @author: Aruna Kushwaha, Cisco Systems, Inc.
|
|
||||||
# @author: Sergey Sudakovich, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
|
|
||||||
|
|
||||||
PROFILE_ID = 'n1kv:profile_id'
|
|
||||||
MULTICAST_IP = 'n1kv:multicast_ip'
|
|
||||||
SEGMENT_ADD = 'n1kv:segment_add'
|
|
||||||
SEGMENT_DEL = 'n1kv:segment_del'
|
|
||||||
MEMBER_SEGMENTS = 'n1kv:member_segments'
|
|
||||||
|
|
||||||
EXTENDED_ATTRIBUTES_2_0 = {
|
|
||||||
'networks': {
|
|
||||||
PROFILE_ID: {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
MULTICAST_IP: {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
SEGMENT_ADD: {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
SEGMENT_DEL: {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
MEMBER_SEGMENTS: {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True},
|
|
||||||
},
|
|
||||||
'ports': {
|
|
||||||
PROFILE_ID: {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED,
|
|
||||||
'is_visible': True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class N1kv(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
"""Extension class supporting N1kv profiles.
|
|
||||||
|
|
||||||
This class is used by neutron's extension framework to make
|
|
||||||
metadata about the n1kv profile extension available to
|
|
||||||
clients. No new resources are defined by this extension. Instead,
|
|
||||||
the existing network resource's request and response messages are
|
|
||||||
extended with attributes in the n1kv profile namespace.
|
|
||||||
|
|
||||||
To create a network based on n1kv profile using the CLI with admin rights:
|
|
||||||
|
|
||||||
(shell) net-create --tenant_id <tenant-id> <net-name> \
|
|
||||||
--n1kv:profile_id <id>
|
|
||||||
(shell) port-create --tenant_id <tenant-id> <net-name> \
|
|
||||||
--n1kv:profile_id <id>
|
|
||||||
|
|
||||||
|
|
||||||
With admin rights, network dictionaries returned from CLI commands
|
|
||||||
will also include n1kv profile attributes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "n1kv"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return "n1kv"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Expose network profile"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://docs.openstack.org/ext/n1kv/api/v2.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2012-11-15T10:00:00-00:00"
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return EXTENDED_ATTRIBUTES_2_0
|
|
||||||
else:
|
|
||||||
return {}
|
|
|
@ -1,103 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Abhishek Raut, Cisco Systems, Inc.
|
|
||||||
# @author: Sergey Sudakovich, Cisco Systems, Inc.
|
|
||||||
# @author: Rudrajit Tapadar, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron import manager
|
|
||||||
|
|
||||||
|
|
||||||
# Attribute Map
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'network_profiles': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'segment_type': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'sub_type': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': True,
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED},
|
|
||||||
'segment_range': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'multicast_ip_range': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True,
|
|
||||||
'default': attributes.ATTR_NOT_SPECIFIED},
|
|
||||||
'multicast_ip_index': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': False, 'default': '0'},
|
|
||||||
'physical_network': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': False, 'default': ''},
|
|
||||||
'add_tenant': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': None},
|
|
||||||
'remove_tenant': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': None},
|
|
||||||
},
|
|
||||||
'network_profile_bindings': {
|
|
||||||
'profile_id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'is_visible': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Network_profile(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "Cisco N1kv Network Profiles"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return 'network_profile'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return ("Profile includes the type of profile for N1kv")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://docs.openstack.org/ext/n1kv/network-profile/api/v2.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2012-07-20T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
"""Returns Extended Resources."""
|
|
||||||
exts = []
|
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
|
||||||
for resource_name in ['network_profile', 'network_profile_binding']:
|
|
||||||
collection_name = resource_name + "s"
|
|
||||||
controller = base.create_resource(
|
|
||||||
collection_name,
|
|
||||||
resource_name,
|
|
||||||
plugin,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP.get(collection_name))
|
|
||||||
ex = extensions.ResourceExtension(collection_name,
|
|
||||||
controller)
|
|
||||||
exts.append(ex)
|
|
||||||
return exts
|
|
|
@ -1,85 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Abhishek Raut, Cisco Systems, Inc.
|
|
||||||
# @author: Sergey Sudakovich, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron import manager
|
|
||||||
|
|
||||||
# Attribute Map
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
'policy_profiles': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'is_visible': True},
|
|
||||||
'name': {'allow_post': False, 'allow_put': False,
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'add_tenant': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': None},
|
|
||||||
'remove_tenant': {'allow_post': True, 'allow_put': True,
|
|
||||||
'is_visible': True, 'default': None},
|
|
||||||
},
|
|
||||||
'policy_profile_bindings': {
|
|
||||||
'profile_id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:regex': attributes.UUID_PATTERN},
|
|
||||||
'is_visible': True},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'is_visible': True},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Policy_profile(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "Cisco Nexus1000V Policy Profiles"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return 'policy_profile'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return "Profile includes the type of profile for N1kv"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
return "http://docs.openstack.org/ext/n1kv/policy-profile/api/v2.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2012-07-20T10:00:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
"""Returns Extended Resources."""
|
|
||||||
exts = []
|
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
|
||||||
for resource_name in ['policy_profile', 'policy_profile_binding']:
|
|
||||||
collection_name = resource_name + "s"
|
|
||||||
controller = base.create_resource(
|
|
||||||
collection_name,
|
|
||||||
resource_name,
|
|
||||||
plugin,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP.get(collection_name))
|
|
||||||
ex = extensions.ResourceExtension(collection_name,
|
|
||||||
controller)
|
|
||||||
exts.append(ex)
|
|
||||||
return exts
|
|
|
@ -1,156 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ying Liu, Cisco Systems, Inc.
|
|
||||||
#
|
|
||||||
|
|
||||||
from webob import exc
|
|
||||||
|
|
||||||
from neutron.api import api_common as common
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as exception
|
|
||||||
from neutron.plugins.cisco.common import cisco_faults as faults
|
|
||||||
from neutron.plugins.cisco.extensions import _qos_view as qos_view
|
|
||||||
from neutron import wsgi
|
|
||||||
|
|
||||||
|
|
||||||
class Qos(extensions.ExtensionDescriptor):
|
|
||||||
"""Qos extension file."""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
"""Returns Ext Resource Name."""
|
|
||||||
return "Cisco qos"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
"""Returns Ext Resource Alias."""
|
|
||||||
return "Cisco qos"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
"""Returns Ext Resource Description."""
|
|
||||||
return "qos includes qos_name and qos_desc"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_namespace(cls):
|
|
||||||
"""Returns Ext Resource Namespace."""
|
|
||||||
return "http://docs.ciscocloud.com/api/ext/qos/v1.0"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
"""Returns Ext Resource update."""
|
|
||||||
return "2011-07-25T13:25:27-06:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
"""Returns Ext Resources."""
|
|
||||||
parent_resource = dict(member_name="tenant",
|
|
||||||
collection_name="extensions/csco/tenants")
|
|
||||||
|
|
||||||
controller = QosController(manager.NeutronManager.get_plugin())
|
|
||||||
return [extensions.ResourceExtension('qoss', controller,
|
|
||||||
parent=parent_resource)]
|
|
||||||
|
|
||||||
|
|
||||||
class QosController(common.NeutronController, wsgi.Controller):
|
|
||||||
"""qos API controller based on NeutronController."""
|
|
||||||
|
|
||||||
_qos_ops_param_list = [
|
|
||||||
{'param-name': 'qos_name', 'required': True},
|
|
||||||
{'param-name': 'qos_desc', 'required': True},
|
|
||||||
]
|
|
||||||
|
|
||||||
_serialization_metadata = {
|
|
||||||
"application/xml": {
|
|
||||||
"attributes": {
|
|
||||||
"qos": ["id", "name"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, plugin):
|
|
||||||
self._resource_name = 'qos'
|
|
||||||
self._plugin = plugin
|
|
||||||
|
|
||||||
def index(self, request, tenant_id):
|
|
||||||
"""Returns a list of qos ids."""
|
|
||||||
return self._items(request, tenant_id, is_detail=False)
|
|
||||||
|
|
||||||
def _items(self, request, tenant_id, is_detail):
|
|
||||||
"""Returns a list of qoss."""
|
|
||||||
qoss = self._plugin.get_all_qoss(tenant_id)
|
|
||||||
builder = qos_view.get_view_builder(request)
|
|
||||||
result = [builder.build(qos, is_detail)['qos'] for qos in qoss]
|
|
||||||
return dict(qoss=result)
|
|
||||||
|
|
||||||
# pylint: disable-msg=E1101
|
|
||||||
def show(self, request, tenant_id, id):
|
|
||||||
"""Returns qos details for the given qos id."""
|
|
||||||
try:
|
|
||||||
qos = self._plugin.get_qos_details(tenant_id, id)
|
|
||||||
builder = qos_view.get_view_builder(request)
|
|
||||||
#build response with details
|
|
||||||
result = builder.build(qos, True)
|
|
||||||
return dict(qoss=result)
|
|
||||||
except exception.QosNotFound as exp:
|
|
||||||
return faults.Fault(faults.QosNotFound(exp))
|
|
||||||
|
|
||||||
def create(self, request, tenant_id):
|
|
||||||
"""Creates a new qos for a given tenant."""
|
|
||||||
#look for qos name in request
|
|
||||||
try:
|
|
||||||
body = self._deserialize(request.body, request.get_content_type())
|
|
||||||
req_body = self._prepare_request_body(body,
|
|
||||||
self._qos_ops_param_list)
|
|
||||||
req_params = req_body[self._resource_name]
|
|
||||||
except exc.HTTPError as exp:
|
|
||||||
return faults.Fault(exp)
|
|
||||||
qos = self._plugin.create_qos(tenant_id,
|
|
||||||
req_params['qos_name'],
|
|
||||||
req_params['qos_desc'])
|
|
||||||
builder = qos_view.get_view_builder(request)
|
|
||||||
result = builder.build(qos)
|
|
||||||
return dict(qoss=result)
|
|
||||||
|
|
||||||
def update(self, request, tenant_id, id):
|
|
||||||
"""Updates the name for the qos with the given id."""
|
|
||||||
try:
|
|
||||||
body = self._deserialize(request.body, request.get_content_type())
|
|
||||||
req_body = self._prepare_request_body(body,
|
|
||||||
self._qos_ops_param_list)
|
|
||||||
req_params = req_body[self._resource_name]
|
|
||||||
except exc.HTTPError as exp:
|
|
||||||
return faults.Fault(exp)
|
|
||||||
try:
|
|
||||||
qos = self._plugin.rename_qos(tenant_id, id,
|
|
||||||
req_params['qos_name'])
|
|
||||||
|
|
||||||
builder = qos_view.get_view_builder(request)
|
|
||||||
result = builder.build(qos, True)
|
|
||||||
return dict(qoss=result)
|
|
||||||
except exception.QosNotFound as exp:
|
|
||||||
return faults.Fault(faults.QosNotFound(exp))
|
|
||||||
|
|
||||||
def delete(self, request, tenant_id, id):
|
|
||||||
"""Destroys the qos with the given id."""
|
|
||||||
try:
|
|
||||||
self._plugin.delete_qos(tenant_id, id)
|
|
||||||
return exc.HTTPOk()
|
|
||||||
except exception.QosNotFound as exp:
|
|
||||||
return faults.Fault(faults.QosNotFound(exp))
|
|
|
@ -1,175 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import inspect
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class L2DevicePluginBase(object):
|
|
||||||
"""Base class for a device-specific plugin.
|
|
||||||
|
|
||||||
An example of a device-specific plugin is a Nexus switch plugin.
|
|
||||||
The network model relies on device-category-specific plugins to perform
|
|
||||||
the configuration on each device.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id,
|
|
||||||
**kwargs):
|
|
||||||
"""Create network.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_network(self, tenant_id, net_id, **kwargs):
|
|
||||||
"""Delete network.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_network(self, tenant_id, net_id, name, **kwargs):
|
|
||||||
"""Update network.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
|
|
||||||
"""Create port.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_port(self, tenant_id, net_id, port_id, **kwargs):
|
|
||||||
"""Delete port.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def update_port(self, tenant_id, net_id, port_id, **kwargs):
|
|
||||||
"""Update port.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
|
|
||||||
**kwargs):
|
|
||||||
"""Plug interface.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
|
|
||||||
"""Unplug interface.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_subnet(self, tenant_id, net_id, ip_version,
|
|
||||||
subnet_cidr, **kwargs):
|
|
||||||
"""Create subnet.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_subnets(self, tenant_id, net_id, **kwargs):
|
|
||||||
"""Get subnets.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_subnet(self, tenant_id, net_id, subnet_id, **kwargs):
|
|
||||||
"""Get subnet.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_subnet(self, tenant_id, net_id, subnet_id, **kwargs):
|
|
||||||
"""Update subnet.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_subnet(self, tenant_id, net_id, subnet_id, **kwargs):
|
|
||||||
"""Delete subnet.
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
:raises:
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, klass):
|
|
||||||
"""Check plugin class.
|
|
||||||
|
|
||||||
The __subclasshook__ method is a class method
|
|
||||||
that will be called every time a class is tested
|
|
||||||
using issubclass(klass, Plugin).
|
|
||||||
In that case, it will check that every method
|
|
||||||
marked with the abstractmethod decorator is
|
|
||||||
provided by the plugin class.
|
|
||||||
"""
|
|
||||||
if cls is L2DevicePluginBase:
|
|
||||||
for method in cls.__abstractmethods__:
|
|
||||||
method_ok = False
|
|
||||||
for base in klass.__mro__:
|
|
||||||
if method in base.__dict__:
|
|
||||||
fn_obj = base.__dict__[method]
|
|
||||||
if inspect.isfunction(fn_obj):
|
|
||||||
abstract_fn_obj = cls.__dict__[method]
|
|
||||||
arg_count = fn_obj.func_code.co_argcount
|
|
||||||
expected_arg_count = \
|
|
||||||
abstract_fn_obj.func_code.co_argcount
|
|
||||||
method_ok = arg_count == expected_arg_count
|
|
||||||
if method_ok:
|
|
||||||
continue
|
|
||||||
return NotImplemented
|
|
||||||
return True
|
|
||||||
return NotImplemented
|
|
|
@ -1,17 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
|
@ -1,553 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
#
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.db import api as db_api
|
|
||||||
from neutron.extensions import portbindings
|
|
||||||
from neutron.extensions import providernet as provider
|
|
||||||
from neutron import neutron_plugin_base_v2
|
|
||||||
from neutron.openstack.common import importutils
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants as const
|
|
||||||
from neutron.plugins.cisco.common import cisco_credentials_v2 as cred
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as cexc
|
|
||||||
from neutron.plugins.cisco.common import config as conf
|
|
||||||
from neutron.plugins.cisco.db import network_db_v2 as cdb
|
|
||||||
from neutron.plugins.openvswitch import ovs_db_v2 as odb
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
|
|
||||||
"""Virtual Physical Switch Model.
|
|
||||||
|
|
||||||
This implementation works with OVS and Nexus plugin for the
|
|
||||||
following topology:
|
|
||||||
One or more servers to a nexus switch.
|
|
||||||
"""
|
|
||||||
__native_bulk_support = True
|
|
||||||
supported_extension_aliases = ["provider", "binding"]
|
|
||||||
_methods_to_delegate = ['create_network_bulk',
|
|
||||||
'get_network', 'get_networks',
|
|
||||||
'create_port_bulk',
|
|
||||||
'get_port', 'get_ports',
|
|
||||||
'create_subnet', 'create_subnet_bulk',
|
|
||||||
'delete_subnet', 'update_subnet',
|
|
||||||
'get_subnet', 'get_subnets',
|
|
||||||
'create_or_update_agent', 'report_state']
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize the segmentation manager.
|
|
||||||
|
|
||||||
Checks which device plugins are configured, and load the inventories
|
|
||||||
those device plugins for which the inventory is configured.
|
|
||||||
"""
|
|
||||||
conf.CiscoConfigOptions()
|
|
||||||
|
|
||||||
self._plugins = {}
|
|
||||||
for key in conf.CISCO_PLUGINS.keys():
|
|
||||||
plugin_obj = conf.CISCO_PLUGINS[key]
|
|
||||||
if plugin_obj is not None:
|
|
||||||
self._plugins[key] = importutils.import_object(plugin_obj)
|
|
||||||
LOG.debug(_("Loaded device plugin %s"),
|
|
||||||
conf.CISCO_PLUGINS[key])
|
|
||||||
|
|
||||||
if ((const.VSWITCH_PLUGIN in self._plugins) and
|
|
||||||
hasattr(self._plugins[const.VSWITCH_PLUGIN],
|
|
||||||
"supported_extension_aliases")):
|
|
||||||
self.supported_extension_aliases.extend(
|
|
||||||
self._plugins[const.VSWITCH_PLUGIN].
|
|
||||||
supported_extension_aliases)
|
|
||||||
# At this point, all the database models should have been loaded. It's
|
|
||||||
# possible that configure_db() may have been called by one of the
|
|
||||||
# plugins loaded in above. Otherwise, this call is to make sure that
|
|
||||||
# the database is initialized
|
|
||||||
db_api.configure_db()
|
|
||||||
|
|
||||||
# Initialize credential store after database initialization
|
|
||||||
cred.Store.initialize()
|
|
||||||
LOG.debug(_("%(module)s.%(name)s init done"),
|
|
||||||
{'module': __name__,
|
|
||||||
'name': self.__class__.__name__})
|
|
||||||
|
|
||||||
# Check whether we have a valid Nexus driver loaded
|
|
||||||
self.is_nexus_plugin = False
|
|
||||||
nexus_driver = conf.CISCO.nexus_driver
|
|
||||||
if nexus_driver.endswith('CiscoNEXUSDriver'):
|
|
||||||
self.is_nexus_plugin = True
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
|
||||||
"""Delegate calls to OVS sub-plugin.
|
|
||||||
|
|
||||||
This delegates the calls to the methods implemented only by the OVS
|
|
||||||
sub-plugin. Note: Currently, bulking is handled by the caller
|
|
||||||
(PluginV2), and this model class expects to receive only non-bulking
|
|
||||||
calls. If, however, a bulking call is made, this will method will
|
|
||||||
delegate the call to the OVS plugin.
|
|
||||||
"""
|
|
||||||
super_getattribute = super(VirtualPhysicalSwitchModelV2,
|
|
||||||
self).__getattribute__
|
|
||||||
methods = super_getattribute('_methods_to_delegate')
|
|
||||||
|
|
||||||
if name in methods:
|
|
||||||
plugin = super_getattribute('_plugins')[const.VSWITCH_PLUGIN]
|
|
||||||
return getattr(plugin, name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return super_getattribute(name)
|
|
||||||
except AttributeError:
|
|
||||||
plugin = super_getattribute('_plugins')[const.VSWITCH_PLUGIN]
|
|
||||||
return getattr(plugin, name)
|
|
||||||
|
|
||||||
def _func_name(self, offset=0):
|
|
||||||
"""Get the name of the calling function."""
|
|
||||||
frame_record = inspect.stack()[1 + offset]
|
|
||||||
func_name = frame_record[3]
|
|
||||||
return func_name
|
|
||||||
|
|
||||||
def _invoke_plugin_per_device(self, plugin_key, function_name,
|
|
||||||
args, **kwargs):
|
|
||||||
"""Invoke plugin per device.
|
|
||||||
|
|
||||||
Invokes a device plugin's relevant functions (based on the
|
|
||||||
plugin implementation) for completing this operation.
|
|
||||||
"""
|
|
||||||
if plugin_key not in self._plugins:
|
|
||||||
LOG.info(_("No %s Plugin loaded"), plugin_key)
|
|
||||||
LOG.info(_("%(plugin_key)s: %(function_name)s with args %(args)s "
|
|
||||||
"ignored"),
|
|
||||||
{'plugin_key': plugin_key,
|
|
||||||
'function_name': function_name,
|
|
||||||
'args': args})
|
|
||||||
else:
|
|
||||||
func = getattr(self._plugins[plugin_key], function_name)
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
def _get_segmentation_id(self, network_id):
|
|
||||||
binding_seg_id = odb.get_network_binding(None, network_id)
|
|
||||||
if not binding_seg_id:
|
|
||||||
raise cexc.NetworkSegmentIDNotFound(net_id=network_id)
|
|
||||||
return binding_seg_id.segmentation_id
|
|
||||||
|
|
||||||
def _get_provider_vlan_id(self, network):
|
|
||||||
if (all(attributes.is_attr_set(network.get(attr))
|
|
||||||
for attr in (provider.NETWORK_TYPE,
|
|
||||||
provider.PHYSICAL_NETWORK,
|
|
||||||
provider.SEGMENTATION_ID))
|
|
||||||
and
|
|
||||||
network[provider.NETWORK_TYPE] == const.NETWORK_TYPE_VLAN):
|
|
||||||
return network[provider.SEGMENTATION_ID]
|
|
||||||
|
|
||||||
def create_network(self, context, network):
|
|
||||||
"""Create network.
|
|
||||||
|
|
||||||
Perform this operation in the context of the configured device
|
|
||||||
plugins.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("create_network() called"))
|
|
||||||
provider_vlan_id = self._get_provider_vlan_id(network[const.NETWORK])
|
|
||||||
args = [context, network]
|
|
||||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
args)
|
|
||||||
# The vswitch plugin did all the verification. If it's a provider
|
|
||||||
# vlan network, save it for the nexus plugin to use later.
|
|
||||||
if provider_vlan_id:
|
|
||||||
network_id = ovs_output[const.NET_ID]
|
|
||||||
cdb.add_provider_network(network_id,
|
|
||||||
const.NETWORK_TYPE_VLAN,
|
|
||||||
provider_vlan_id)
|
|
||||||
LOG.debug(_("Provider network added to DB: %(network_id)s, "
|
|
||||||
"%(vlan_id)s"),
|
|
||||||
{'network_id': network_id, 'vlan_id': provider_vlan_id})
|
|
||||||
return ovs_output
|
|
||||||
|
|
||||||
def update_network(self, context, id, network):
|
|
||||||
"""Update network.
|
|
||||||
|
|
||||||
Perform this operation in the context of the configured device
|
|
||||||
plugins.
|
|
||||||
|
|
||||||
Note that the Nexus sub-plugin does not need to be notified
|
|
||||||
(and the Nexus switch does not need to be [re]configured)
|
|
||||||
for an update network operation because the Nexus sub-plugin
|
|
||||||
is agnostic of all network-level attributes except the
|
|
||||||
segmentation ID. Furthermore, updating of the segmentation ID
|
|
||||||
is not supported by the OVS plugin since it is considered a
|
|
||||||
provider attribute, so it is not supported by this method.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("update_network() called"))
|
|
||||||
|
|
||||||
# We can only support updating of provider attributes if all the
|
|
||||||
# configured sub-plugins support it. Currently we have no method
|
|
||||||
# in place for checking whether a sub-plugin supports it,
|
|
||||||
# so assume not.
|
|
||||||
provider._raise_if_updates_provider_attributes(network['network'])
|
|
||||||
|
|
||||||
args = [context, id, network]
|
|
||||||
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
args)
|
|
||||||
|
|
||||||
def delete_network(self, context, id):
|
|
||||||
"""Delete network.
|
|
||||||
|
|
||||||
Perform this operation in the context of the configured device
|
|
||||||
plugins.
|
|
||||||
"""
|
|
||||||
args = [context, id]
|
|
||||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
args)
|
|
||||||
if cdb.remove_provider_network(id):
|
|
||||||
LOG.debug(_("Provider network removed from DB: %s"), id)
|
|
||||||
return ovs_output
|
|
||||||
|
|
||||||
def get_network(self, context, id, fields=None):
|
|
||||||
"""Get network. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def get_networks(self, context, filters=None, fields=None,
|
|
||||||
sorts=None, limit=None, marker=None, page_reverse=False):
|
|
||||||
"""Get networks. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def _invoke_nexus_for_net_create(self, context, tenant_id, net_id,
|
|
||||||
instance_id, host_id):
|
|
||||||
if not self.is_nexus_plugin:
|
|
||||||
return False
|
|
||||||
|
|
||||||
network = self.get_network(context, net_id)
|
|
||||||
vlan_id = self._get_segmentation_id(net_id)
|
|
||||||
vlan_name = conf.CISCO.vlan_name_prefix + str(vlan_id)
|
|
||||||
network[const.NET_VLAN_ID] = vlan_id
|
|
||||||
network[const.NET_VLAN_NAME] = vlan_name
|
|
||||||
attachment = {
|
|
||||||
const.TENANT_ID: tenant_id,
|
|
||||||
const.INSTANCE_ID: instance_id,
|
|
||||||
const.HOST_NAME: host_id,
|
|
||||||
}
|
|
||||||
self._invoke_plugin_per_device(
|
|
||||||
const.NEXUS_PLUGIN,
|
|
||||||
'create_network',
|
|
||||||
[network, attachment])
|
|
||||||
|
|
||||||
def _check_valid_port_device_owner(self, port):
|
|
||||||
"""Check the port for valid device_owner.
|
|
||||||
|
|
||||||
Don't call the nexus plugin for router and dhcp
|
|
||||||
port owners.
|
|
||||||
"""
|
|
||||||
return port['device_owner'].startswith('compute')
|
|
||||||
|
|
||||||
def _get_port_host_id_from_bindings(self, port):
|
|
||||||
"""Get host_id from portbindings."""
|
|
||||||
host_id = None
|
|
||||||
|
|
||||||
if (portbindings.HOST_ID in port and
|
|
||||||
attributes.is_attr_set(port[portbindings.HOST_ID])):
|
|
||||||
host_id = port[portbindings.HOST_ID]
|
|
||||||
|
|
||||||
return host_id
|
|
||||||
|
|
||||||
def create_port(self, context, port):
|
|
||||||
"""Create port.
|
|
||||||
|
|
||||||
Perform this operation in the context of the configured device
|
|
||||||
plugins.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("create_port() called"))
|
|
||||||
args = [context, port]
|
|
||||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
args)
|
|
||||||
instance_id = port['port']['device_id']
|
|
||||||
|
|
||||||
# Only call nexus plugin if there's a valid instance_id, host_id
|
|
||||||
# and device_owner
|
|
||||||
try:
|
|
||||||
host_id = self._get_port_host_id_from_bindings(port['port'])
|
|
||||||
if (instance_id and host_id and
|
|
||||||
self._check_valid_port_device_owner(port['port'])):
|
|
||||||
net_id = port['port']['network_id']
|
|
||||||
tenant_id = port['port']['tenant_id']
|
|
||||||
self._invoke_nexus_for_net_create(
|
|
||||||
context, tenant_id, net_id, instance_id, host_id)
|
|
||||||
except Exception:
|
|
||||||
# Create network on the Nexus plugin has failed, so we need
|
|
||||||
# to rollback the port creation on the VSwitch plugin.
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
try:
|
|
||||||
id = ovs_output['id']
|
|
||||||
args = [context, id]
|
|
||||||
ovs_output = self._invoke_plugin_per_device(
|
|
||||||
const.VSWITCH_PLUGIN,
|
|
||||||
'delete_port',
|
|
||||||
args)
|
|
||||||
finally:
|
|
||||||
# Re-raise the original exception
|
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
|
||||||
return ovs_output
|
|
||||||
|
|
||||||
def get_port(self, context, id, fields=None):
|
|
||||||
"""Get port. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def get_ports(self, context, filters=None, fields=None):
|
|
||||||
"""Get ports. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def _check_nexus_net_create_needed(self, new_port, old_port):
|
|
||||||
"""Check if nexus plugin should be invoked for net_create.
|
|
||||||
|
|
||||||
In the following cases, the plugin should be invoked:
|
|
||||||
-- a port is attached to a VM instance. The old host id is None
|
|
||||||
-- VM migration. The old host id has a valid value
|
|
||||||
|
|
||||||
When the plugin needs to be invoked, return the old_host_id,
|
|
||||||
and a list of calling arguments.
|
|
||||||
Otherwise, return '' for old host id and an empty list
|
|
||||||
"""
|
|
||||||
old_device_id = old_port['device_id']
|
|
||||||
new_device_id = new_port.get('device_id')
|
|
||||||
new_host_id = self._get_port_host_id_from_bindings(new_port)
|
|
||||||
tenant_id = old_port['tenant_id']
|
|
||||||
net_id = old_port['network_id']
|
|
||||||
old_host_id = self._get_port_host_id_from_bindings(old_port)
|
|
||||||
|
|
||||||
LOG.debug(_("tenant_id: %(tid)s, net_id: %(nid)s, "
|
|
||||||
"old_device_id: %(odi)s, new_device_id: %(ndi)s, "
|
|
||||||
"old_host_id: %(ohi)s, new_host_id: %(nhi)s, "
|
|
||||||
"old_device_owner: %(odo)s, new_device_owner: %(ndo)s"),
|
|
||||||
{'tid': tenant_id, 'nid': net_id,
|
|
||||||
'odi': old_device_id, 'ndi': new_device_id,
|
|
||||||
'ohi': old_host_id, 'nhi': new_host_id,
|
|
||||||
'odo': old_port.get('device_owner'),
|
|
||||||
'ndo': new_port.get('device_owner')})
|
|
||||||
|
|
||||||
# A port is attached to an instance
|
|
||||||
if (new_device_id and not old_device_id and new_host_id and
|
|
||||||
self._check_valid_port_device_owner(new_port)):
|
|
||||||
return '', [tenant_id, net_id, new_device_id, new_host_id]
|
|
||||||
|
|
||||||
# An instance is being migrated
|
|
||||||
if (old_device_id and old_host_id and new_host_id != old_host_id and
|
|
||||||
self._check_valid_port_device_owner(old_port)):
|
|
||||||
return old_host_id, [tenant_id, net_id, old_device_id, new_host_id]
|
|
||||||
|
|
||||||
# no need to invoke the plugin
|
|
||||||
return '', []
|
|
||||||
|
|
||||||
def update_port(self, context, id, port):
|
|
||||||
"""Update port.
|
|
||||||
|
|
||||||
Perform this operation in the context of the configured device
|
|
||||||
plugins.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("update_port() called"))
|
|
||||||
old_port = self.get_port(context, id)
|
|
||||||
args = [context, id, port]
|
|
||||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
args)
|
|
||||||
try:
|
|
||||||
# Check if the nexus plugin needs to be invoked
|
|
||||||
old_host_id, create_args = self._check_nexus_net_create_needed(
|
|
||||||
port['port'], old_port)
|
|
||||||
|
|
||||||
# In the case of migration, invoke it to remove
|
|
||||||
# the previous port binding
|
|
||||||
if old_host_id:
|
|
||||||
vlan_id = self._get_segmentation_id(old_port['network_id'])
|
|
||||||
delete_args = [old_port['device_id'], vlan_id]
|
|
||||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
|
||||||
"delete_port",
|
|
||||||
delete_args)
|
|
||||||
|
|
||||||
# Invoke the Nexus plugin to create a net and/or new port binding
|
|
||||||
if create_args:
|
|
||||||
self._invoke_nexus_for_net_create(context, *create_args)
|
|
||||||
|
|
||||||
return ovs_output
|
|
||||||
except Exception:
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
LOG.error(_("Unable to update port '%s' on Nexus switch"),
|
|
||||||
old_port['name'], exc_info=exc_info)
|
|
||||||
try:
|
|
||||||
# Roll back vSwitch plugin to original port attributes.
|
|
||||||
args = [context, id, {'port': old_port}]
|
|
||||||
self._invoke_plugin_per_device(
|
|
||||||
const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
args)
|
|
||||||
finally:
|
|
||||||
# Re-raise the original exception
|
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
|
||||||
|
|
||||||
def delete_port(self, context, id, l3_port_check=True):
|
|
||||||
"""Delete port.
|
|
||||||
|
|
||||||
Perform this operation in the context of the configured device
|
|
||||||
plugins.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("delete_port() called"))
|
|
||||||
port = self.get_port(context, id)
|
|
||||||
|
|
||||||
host_id = self._get_port_host_id_from_bindings(port)
|
|
||||||
|
|
||||||
if (self.is_nexus_plugin and host_id and
|
|
||||||
self._check_valid_port_device_owner(port)):
|
|
||||||
vlan_id = self._get_segmentation_id(port['network_id'])
|
|
||||||
n_args = [port['device_id'], vlan_id]
|
|
||||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
n_args)
|
|
||||||
try:
|
|
||||||
args = [context, id]
|
|
||||||
ovs_output = self._invoke_plugin_per_device(
|
|
||||||
const.VSWITCH_PLUGIN, self._func_name(),
|
|
||||||
args, l3_port_check=l3_port_check)
|
|
||||||
except Exception:
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
# Roll back the delete port on the Nexus plugin
|
|
||||||
try:
|
|
||||||
tenant_id = port['tenant_id']
|
|
||||||
net_id = port['network_id']
|
|
||||||
instance_id = port['device_id']
|
|
||||||
host_id = port[portbindings.HOST_ID]
|
|
||||||
self._invoke_nexus_for_net_create(context, tenant_id, net_id,
|
|
||||||
instance_id, host_id)
|
|
||||||
finally:
|
|
||||||
# Raise the original exception.
|
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
|
||||||
|
|
||||||
return ovs_output
|
|
||||||
|
|
||||||
def add_router_interface(self, context, router_id, interface_info):
|
|
||||||
"""Add a router interface on a subnet.
|
|
||||||
|
|
||||||
Only invoke the Nexus plugin to create SVI if L3 support on
|
|
||||||
the Nexus switches is enabled and a Nexus plugin is loaded,
|
|
||||||
otherwise send it to the vswitch plugin
|
|
||||||
"""
|
|
||||||
if (conf.CISCO.nexus_l3_enable and self.is_nexus_plugin):
|
|
||||||
LOG.debug(_("L3 enabled on Nexus plugin, create SVI on switch"))
|
|
||||||
if 'subnet_id' not in interface_info:
|
|
||||||
raise cexc.SubnetNotSpecified()
|
|
||||||
if 'port_id' in interface_info:
|
|
||||||
raise cexc.PortIdForNexusSvi()
|
|
||||||
subnet = self.get_subnet(context, interface_info['subnet_id'])
|
|
||||||
gateway_ip = subnet['gateway_ip']
|
|
||||||
# Get gateway IP address and netmask
|
|
||||||
cidr = subnet['cidr']
|
|
||||||
netmask = cidr.split('/', 1)[1]
|
|
||||||
gateway_ip = gateway_ip + '/' + netmask
|
|
||||||
network_id = subnet['network_id']
|
|
||||||
vlan_id = self._get_segmentation_id(network_id)
|
|
||||||
vlan_name = conf.CISCO.vlan_name_prefix + str(vlan_id)
|
|
||||||
|
|
||||||
n_args = [vlan_name, vlan_id, subnet['id'], gateway_ip, router_id]
|
|
||||||
return self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
n_args)
|
|
||||||
else:
|
|
||||||
LOG.debug(_("L3 disabled or not Nexus plugin, send to vswitch"))
|
|
||||||
n_args = [context, router_id, interface_info]
|
|
||||||
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
n_args)
|
|
||||||
|
|
||||||
def remove_router_interface(self, context, router_id, interface_info):
|
|
||||||
"""Remove a router interface.
|
|
||||||
|
|
||||||
Only invoke the Nexus plugin to delete SVI if L3 support on
|
|
||||||
the Nexus switches is enabled and a Nexus plugin is loaded,
|
|
||||||
otherwise send it to the vswitch plugin
|
|
||||||
"""
|
|
||||||
if (conf.CISCO.nexus_l3_enable and self.is_nexus_plugin):
|
|
||||||
LOG.debug(_("L3 enabled on Nexus plugin, delete SVI from switch"))
|
|
||||||
|
|
||||||
subnet = self.get_subnet(context, interface_info['subnet_id'])
|
|
||||||
network_id = subnet['network_id']
|
|
||||||
vlan_id = self._get_segmentation_id(network_id)
|
|
||||||
n_args = [vlan_id, router_id]
|
|
||||||
|
|
||||||
return self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
n_args)
|
|
||||||
else:
|
|
||||||
LOG.debug(_("L3 disabled or not Nexus plugin, send to vswitch"))
|
|
||||||
n_args = [context, router_id, interface_info]
|
|
||||||
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
|
||||||
self._func_name(),
|
|
||||||
n_args)
|
|
||||||
|
|
||||||
def create_subnet(self, context, subnet):
|
|
||||||
"""Create subnet. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def update_subnet(self, context, id, subnet):
|
|
||||||
"""Update subnet. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def get_subnet(self, context, id, fields=None):
|
|
||||||
"""Get subnet. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def delete_subnet(self, context, id, kwargs):
|
|
||||||
"""Delete subnet. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
||||||
|
|
||||||
def get_subnets(self, context, filters=None, fields=None,
|
|
||||||
sorts=None, limit=None, marker=None, page_reverse=False):
|
|
||||||
"""Get subnets. This method is delegated to the vswitch plugin.
|
|
||||||
|
|
||||||
This method is included here to satisfy abstract method requirements.
|
|
||||||
"""
|
|
||||||
pass # pragma no cover
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Abhishek Raut, Cisco Systems, Inc.
|
|
||||||
#
|
|
|
@ -1,541 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cisco 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.
|
|
||||||
#
|
|
||||||
# @author: Abhishek Raut, Cisco Systems, Inc.
|
|
||||||
# @author: Rudrajit Tapadar, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import eventlet
|
|
||||||
import netaddr
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from neutron.common import exceptions as n_exc
|
|
||||||
from neutron.extensions import providernet
|
|
||||||
from neutron.openstack.common import jsonutils
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants as c_const
|
|
||||||
from neutron.plugins.cisco.common import cisco_credentials_v2 as c_cred
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as c_exc
|
|
||||||
from neutron.plugins.cisco.common import config as c_conf
|
|
||||||
from neutron.plugins.cisco.db import network_db_v2
|
|
||||||
from neutron.plugins.cisco.extensions import n1kv
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Client for the Cisco Nexus1000V Neutron Plugin.
|
|
||||||
|
|
||||||
This client implements functions to communicate with
|
|
||||||
Cisco Nexus1000V VSM.
|
|
||||||
|
|
||||||
For every Neutron objects, Cisco Nexus1000V Neutron Plugin
|
|
||||||
creates a corresponding object in the controller (Cisco
|
|
||||||
Nexus1000V VSM).
|
|
||||||
|
|
||||||
CONCEPTS:
|
|
||||||
|
|
||||||
Following are few concepts used in Nexus1000V VSM:
|
|
||||||
|
|
||||||
port-profiles:
|
|
||||||
Policy profiles correspond to port profiles on Nexus1000V VSM.
|
|
||||||
Port profiles are the primary mechanism by which network policy is
|
|
||||||
defined and applied to switch interfaces in a Nexus 1000V system.
|
|
||||||
|
|
||||||
network-segment:
|
|
||||||
Each network-segment represents a broadcast domain.
|
|
||||||
|
|
||||||
network-segment-pool:
|
|
||||||
A network-segment-pool contains one or more network-segments.
|
|
||||||
|
|
||||||
logical-network:
|
|
||||||
A logical-network contains one or more network-segment-pools.
|
|
||||||
|
|
||||||
bridge-domain:
|
|
||||||
A bridge-domain is created when the network-segment is of type VXLAN.
|
|
||||||
Each VXLAN <--> VLAN combination can be thought of as a bridge domain.
|
|
||||||
|
|
||||||
ip-pool:
|
|
||||||
Each ip-pool represents a subnet on the Nexus1000V VSM.
|
|
||||||
|
|
||||||
vm-network:
|
|
||||||
vm-network refers to a network-segment and policy-profile.
|
|
||||||
It maintains a list of ports that uses the network-segment and
|
|
||||||
policy-profile this vm-network refers to.
|
|
||||||
|
|
||||||
events:
|
|
||||||
Events correspond to commands that are logged on Nexus1000V VSM.
|
|
||||||
Events are used to poll for a certain resource on Nexus1000V VSM.
|
|
||||||
Event type of port_profile: Return all updates/create/deletes
|
|
||||||
of port profiles from the VSM.
|
|
||||||
Event type of port_profile_update: Return only updates regarding
|
|
||||||
policy-profiles.
|
|
||||||
Event type of port_profile_delete: Return only deleted policy profiles.
|
|
||||||
|
|
||||||
|
|
||||||
WORK FLOW:
|
|
||||||
|
|
||||||
For every network profile a corresponding logical-network and
|
|
||||||
a network-segment-pool, under this logical-network, will be created.
|
|
||||||
|
|
||||||
For every network created from a given network profile, a
|
|
||||||
network-segment will be added to the network-segment-pool corresponding
|
|
||||||
to that network profile.
|
|
||||||
|
|
||||||
A port is created on a network and associated with a policy-profile.
|
|
||||||
Hence for every unique combination of a network and a policy-profile, a
|
|
||||||
unique vm-network will be created and a reference to the port will be
|
|
||||||
added. If the same combination of network and policy-profile is used by
|
|
||||||
another port, the references to that port will be added to the same
|
|
||||||
vm-network.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Define paths for the URI where the client connects for HTTP requests.
|
|
||||||
port_profiles_path = "/virtual-port-profile"
|
|
||||||
network_segment_path = "/network-segment/%s"
|
|
||||||
network_segment_pool_path = "/network-segment-pool/%s"
|
|
||||||
ip_pool_path = "/ip-pool-template/%s"
|
|
||||||
ports_path = "/kvm/vm-network/%s/ports"
|
|
||||||
port_path = "/kvm/vm-network/%s/ports/%s"
|
|
||||||
vm_networks_path = "/kvm/vm-network"
|
|
||||||
vm_network_path = "/kvm/vm-network/%s"
|
|
||||||
bridge_domains_path = "/kvm/bridge-domain"
|
|
||||||
bridge_domain_path = "/kvm/bridge-domain/%s"
|
|
||||||
logical_network_path = "/logical-network/%s"
|
|
||||||
events_path = "/kvm/events"
|
|
||||||
clusters_path = "/cluster"
|
|
||||||
encap_profiles_path = "/encapsulation-profile"
|
|
||||||
encap_profile_path = "/encapsulation-profile/%s"
|
|
||||||
|
|
||||||
pool = eventlet.GreenPool(c_conf.CISCO_N1K.http_pool_size)
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
"""Initialize a new client for the plugin."""
|
|
||||||
self.format = 'json'
|
|
||||||
self.hosts = self._get_vsm_hosts()
|
|
||||||
self.action_prefix = 'http://%s/api/n1k' % self.hosts[0]
|
|
||||||
self.timeout = c_const.DEFAULT_HTTP_TIMEOUT
|
|
||||||
|
|
||||||
def list_port_profiles(self):
|
|
||||||
"""
|
|
||||||
Fetch all policy profiles from the VSM.
|
|
||||||
|
|
||||||
:returns: JSON string
|
|
||||||
"""
|
|
||||||
return self._get(self.port_profiles_path)
|
|
||||||
|
|
||||||
def create_bridge_domain(self, network, overlay_subtype):
|
|
||||||
"""
|
|
||||||
Create a bridge domain on VSM.
|
|
||||||
|
|
||||||
:param network: network dict
|
|
||||||
:param overlay_subtype: string representing subtype of overlay network
|
|
||||||
"""
|
|
||||||
body = {'name': network['id'] + c_const.BRIDGE_DOMAIN_SUFFIX,
|
|
||||||
'segmentId': network[providernet.SEGMENTATION_ID],
|
|
||||||
'subType': overlay_subtype,
|
|
||||||
'tenantId': network['tenant_id']}
|
|
||||||
if overlay_subtype == c_const.NETWORK_SUBTYPE_NATIVE_VXLAN:
|
|
||||||
body['groupIp'] = network[n1kv.MULTICAST_IP]
|
|
||||||
return self._post(self.bridge_domains_path,
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def delete_bridge_domain(self, name):
|
|
||||||
"""
|
|
||||||
Delete a bridge domain on VSM.
|
|
||||||
|
|
||||||
:param name: name of the bridge domain to be deleted
|
|
||||||
"""
|
|
||||||
return self._delete(self.bridge_domain_path % name)
|
|
||||||
|
|
||||||
def create_network_segment(self, network, network_profile):
|
|
||||||
"""
|
|
||||||
Create a network segment on the VSM.
|
|
||||||
|
|
||||||
:param network: network dict
|
|
||||||
:param network_profile: network profile dict
|
|
||||||
"""
|
|
||||||
body = {'publishName': network['id'],
|
|
||||||
'description': network['name'],
|
|
||||||
'id': network['id'],
|
|
||||||
'tenantId': network['tenant_id'],
|
|
||||||
'networkSegmentPool': network_profile['id'], }
|
|
||||||
if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VLAN:
|
|
||||||
body['vlan'] = network[providernet.SEGMENTATION_ID]
|
|
||||||
elif network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_OVERLAY:
|
|
||||||
body['bridgeDomain'] = (network['id'] +
|
|
||||||
c_const.BRIDGE_DOMAIN_SUFFIX)
|
|
||||||
if network_profile['segment_type'] == c_const.NETWORK_TYPE_TRUNK:
|
|
||||||
body['mode'] = c_const.NETWORK_TYPE_TRUNK
|
|
||||||
body['segmentType'] = network_profile['sub_type']
|
|
||||||
if network_profile['sub_type'] == c_const.NETWORK_TYPE_VLAN:
|
|
||||||
body['addSegments'] = network['add_segment_list']
|
|
||||||
body['delSegments'] = network['del_segment_list']
|
|
||||||
else:
|
|
||||||
body['encapProfile'] = (network['id'] +
|
|
||||||
c_const.ENCAPSULATION_PROFILE_SUFFIX)
|
|
||||||
else:
|
|
||||||
body['mode'] = 'access'
|
|
||||||
body['segmentType'] = network_profile['segment_type']
|
|
||||||
return self._post(self.network_segment_path % network['id'],
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def update_network_segment(self, network_segment_id, body):
|
|
||||||
"""
|
|
||||||
Update a network segment on the VSM.
|
|
||||||
|
|
||||||
Network segment on VSM can be updated to associate it with an ip-pool
|
|
||||||
or update its description and segment id.
|
|
||||||
|
|
||||||
:param network_segment_id: UUID representing the network segment
|
|
||||||
:param body: dict of arguments to be updated
|
|
||||||
"""
|
|
||||||
return self._post(self.network_segment_path % network_segment_id,
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def delete_network_segment(self, network_segment_id):
|
|
||||||
"""
|
|
||||||
Delete a network segment on the VSM.
|
|
||||||
|
|
||||||
:param network_segment_id: UUID representing the network segment
|
|
||||||
"""
|
|
||||||
return self._delete(self.network_segment_path % network_segment_id)
|
|
||||||
|
|
||||||
def create_logical_network(self, network_profile, tenant_id):
|
|
||||||
"""
|
|
||||||
Create a logical network on the VSM.
|
|
||||||
|
|
||||||
:param network_profile: network profile dict
|
|
||||||
:param tenant_id: UUID representing the tenant
|
|
||||||
"""
|
|
||||||
LOG.debug(_("Logical network"))
|
|
||||||
body = {'description': network_profile['name'],
|
|
||||||
'tenantId': tenant_id}
|
|
||||||
logical_network_name = (network_profile['id'] +
|
|
||||||
c_const.LOGICAL_NETWORK_SUFFIX)
|
|
||||||
return self._post(self.logical_network_path % logical_network_name,
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def delete_logical_network(self, logical_network_name):
|
|
||||||
"""
|
|
||||||
Delete a logical network on VSM.
|
|
||||||
|
|
||||||
:param logical_network_name: string representing name of the logical
|
|
||||||
network
|
|
||||||
"""
|
|
||||||
return self._delete(
|
|
||||||
self.logical_network_path % logical_network_name)
|
|
||||||
|
|
||||||
def create_network_segment_pool(self, network_profile, tenant_id):
|
|
||||||
"""
|
|
||||||
Create a network segment pool on the VSM.
|
|
||||||
|
|
||||||
:param network_profile: network profile dict
|
|
||||||
:param tenant_id: UUID representing the tenant
|
|
||||||
"""
|
|
||||||
LOG.debug(_("network_segment_pool"))
|
|
||||||
logical_network_name = (network_profile['id'] +
|
|
||||||
c_const.LOGICAL_NETWORK_SUFFIX)
|
|
||||||
body = {'name': network_profile['name'],
|
|
||||||
'description': network_profile['name'],
|
|
||||||
'id': network_profile['id'],
|
|
||||||
'logicalNetwork': logical_network_name,
|
|
||||||
'tenantId': tenant_id}
|
|
||||||
return self._post(
|
|
||||||
self.network_segment_pool_path % network_profile['id'],
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def update_network_segment_pool(self, network_profile):
|
|
||||||
"""
|
|
||||||
Update a network segment pool on the VSM.
|
|
||||||
|
|
||||||
:param network_profile: network profile dict
|
|
||||||
"""
|
|
||||||
body = {'name': network_profile['name'],
|
|
||||||
'description': network_profile['name']}
|
|
||||||
return self._post(self.network_segment_pool_path %
|
|
||||||
network_profile['id'], body=body)
|
|
||||||
|
|
||||||
def delete_network_segment_pool(self, network_segment_pool_id):
|
|
||||||
"""
|
|
||||||
Delete a network segment pool on the VSM.
|
|
||||||
|
|
||||||
:param network_segment_pool_id: UUID representing the network
|
|
||||||
segment pool
|
|
||||||
"""
|
|
||||||
return self._delete(self.network_segment_pool_path %
|
|
||||||
network_segment_pool_id)
|
|
||||||
|
|
||||||
def create_ip_pool(self, subnet):
|
|
||||||
"""
|
|
||||||
Create an ip-pool on the VSM.
|
|
||||||
|
|
||||||
:param subnet: subnet dict
|
|
||||||
"""
|
|
||||||
if subnet['cidr']:
|
|
||||||
try:
|
|
||||||
ip = netaddr.IPNetwork(subnet['cidr'])
|
|
||||||
netmask = str(ip.netmask)
|
|
||||||
network_address = str(ip.network)
|
|
||||||
except (ValueError, netaddr.AddrFormatError):
|
|
||||||
msg = _("Invalid input for CIDR")
|
|
||||||
raise n_exc.InvalidInput(error_message=msg)
|
|
||||||
else:
|
|
||||||
netmask = network_address = ""
|
|
||||||
|
|
||||||
if subnet['allocation_pools']:
|
|
||||||
address_range_start = subnet['allocation_pools'][0]['start']
|
|
||||||
address_range_end = subnet['allocation_pools'][0]['end']
|
|
||||||
else:
|
|
||||||
address_range_start = None
|
|
||||||
address_range_end = None
|
|
||||||
|
|
||||||
body = {'addressRangeStart': address_range_start,
|
|
||||||
'addressRangeEnd': address_range_end,
|
|
||||||
'ipAddressSubnet': netmask,
|
|
||||||
'description': subnet['name'],
|
|
||||||
'gateway': subnet['gateway_ip'],
|
|
||||||
'dhcp': subnet['enable_dhcp'],
|
|
||||||
'dnsServersList': subnet['dns_nameservers'],
|
|
||||||
'networkAddress': network_address,
|
|
||||||
'tenantId': subnet['tenant_id']}
|
|
||||||
return self._post(self.ip_pool_path % subnet['id'],
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def update_ip_pool(self, subnet):
|
|
||||||
"""
|
|
||||||
Update an ip-pool on the VSM.
|
|
||||||
|
|
||||||
:param subnet: subnet dictionary
|
|
||||||
"""
|
|
||||||
body = {'description': subnet['name'],
|
|
||||||
'dhcp': subnet['enable_dhcp'],
|
|
||||||
'dnsServersList': subnet['dns_nameservers']}
|
|
||||||
return self._post(self.ip_pool_path % subnet['id'],
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def delete_ip_pool(self, subnet_id):
|
|
||||||
"""
|
|
||||||
Delete an ip-pool on the VSM.
|
|
||||||
|
|
||||||
:param subnet_id: UUID representing the subnet
|
|
||||||
"""
|
|
||||||
return self._delete(self.ip_pool_path % subnet_id)
|
|
||||||
|
|
||||||
def create_vm_network(self,
|
|
||||||
port,
|
|
||||||
vm_network_name,
|
|
||||||
policy_profile):
|
|
||||||
"""
|
|
||||||
Create a VM network on the VSM.
|
|
||||||
|
|
||||||
:param port: port dict
|
|
||||||
:param vm_network_name: name of the VM network
|
|
||||||
:param policy_profile: policy profile dict
|
|
||||||
"""
|
|
||||||
body = {'name': vm_network_name,
|
|
||||||
'networkSegmentId': port['network_id'],
|
|
||||||
'networkSegment': port['network_id'],
|
|
||||||
'portProfile': policy_profile['name'],
|
|
||||||
'portProfileId': policy_profile['id'],
|
|
||||||
'tenantId': port['tenant_id'],
|
|
||||||
'portId': port['id'],
|
|
||||||
'macAddress': port['mac_address'],
|
|
||||||
}
|
|
||||||
if port.get('fixed_ips'):
|
|
||||||
body['ipAddress'] = port['fixed_ips'][0]['ip_address']
|
|
||||||
body['subnetId'] = port['fixed_ips'][0]['subnet_id']
|
|
||||||
return self._post(self.vm_networks_path,
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def delete_vm_network(self, vm_network_name):
|
|
||||||
"""
|
|
||||||
Delete a VM network on the VSM.
|
|
||||||
|
|
||||||
:param vm_network_name: name of the VM network
|
|
||||||
"""
|
|
||||||
return self._delete(self.vm_network_path % vm_network_name)
|
|
||||||
|
|
||||||
def create_n1kv_port(self, port, vm_network_name):
|
|
||||||
"""
|
|
||||||
Create a port on the VSM.
|
|
||||||
|
|
||||||
:param port: port dict
|
|
||||||
:param vm_network_name: name of the VM network which imports this port
|
|
||||||
"""
|
|
||||||
body = {'id': port['id'],
|
|
||||||
'macAddress': port['mac_address']}
|
|
||||||
if port.get('fixed_ips'):
|
|
||||||
body['ipAddress'] = port['fixed_ips'][0]['ip_address']
|
|
||||||
body['subnetId'] = port['fixed_ips'][0]['subnet_id']
|
|
||||||
return self._post(self.ports_path % vm_network_name,
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def update_n1kv_port(self, vm_network_name, port_id, body):
|
|
||||||
"""
|
|
||||||
Update a port on the VSM.
|
|
||||||
|
|
||||||
Update the mac address associated with the port
|
|
||||||
|
|
||||||
:param vm_network_name: name of the VM network which imports this port
|
|
||||||
:param port_id: UUID of the port
|
|
||||||
:param body: dict of the arguments to be updated
|
|
||||||
"""
|
|
||||||
return self._post(self.port_path % (vm_network_name, port_id),
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def delete_n1kv_port(self, vm_network_name, port_id):
|
|
||||||
"""
|
|
||||||
Delete a port on the VSM.
|
|
||||||
|
|
||||||
:param vm_network_name: name of the VM network which imports this port
|
|
||||||
:param port_id: UUID of the port
|
|
||||||
"""
|
|
||||||
return self._delete(self.port_path % (vm_network_name, port_id))
|
|
||||||
|
|
||||||
def _do_request(self, method, action, body=None,
|
|
||||||
headers=None):
|
|
||||||
"""
|
|
||||||
Perform the HTTP request.
|
|
||||||
|
|
||||||
The response is in either JSON format or plain text. A GET method will
|
|
||||||
invoke a JSON response while a PUT/POST/DELETE returns message from the
|
|
||||||
VSM in plain text format.
|
|
||||||
Exception is raised when VSM replies with an INTERNAL SERVER ERROR HTTP
|
|
||||||
status code (500) i.e. an error has occurred on the VSM or SERVICE
|
|
||||||
UNAVAILABLE (503) i.e. VSM is not reachable.
|
|
||||||
|
|
||||||
:param method: type of the HTTP request. POST, GET, PUT or DELETE
|
|
||||||
:param action: path to which the client makes request
|
|
||||||
:param body: dict for arguments which are sent as part of the request
|
|
||||||
:param headers: header for the HTTP request
|
|
||||||
:returns: JSON or plain text in HTTP response
|
|
||||||
"""
|
|
||||||
action = self.action_prefix + action
|
|
||||||
if not headers and self.hosts:
|
|
||||||
headers = self._get_auth_header(self.hosts[0])
|
|
||||||
headers['Content-Type'] = self._set_content_type('json')
|
|
||||||
headers['Accept'] = self._set_content_type('json')
|
|
||||||
if body:
|
|
||||||
body = jsonutils.dumps(body, indent=2)
|
|
||||||
LOG.debug(_("req: %s"), body)
|
|
||||||
try:
|
|
||||||
resp = self.pool.spawn(requests.request,
|
|
||||||
method,
|
|
||||||
url=action,
|
|
||||||
data=body,
|
|
||||||
headers=headers,
|
|
||||||
timeout=self.timeout).wait()
|
|
||||||
except Exception as e:
|
|
||||||
raise c_exc.VSMConnectionFailed(reason=e)
|
|
||||||
LOG.debug(_("status_code %s"), resp.status_code)
|
|
||||||
if resp.status_code == requests.codes.OK:
|
|
||||||
if 'application/json' in resp.headers['content-type']:
|
|
||||||
try:
|
|
||||||
return resp.json()
|
|
||||||
except ValueError:
|
|
||||||
return {}
|
|
||||||
elif 'text/plain' in resp.headers['content-type']:
|
|
||||||
LOG.debug(_("VSM: %s"), resp.text)
|
|
||||||
else:
|
|
||||||
raise c_exc.VSMError(reason=resp.text)
|
|
||||||
|
|
||||||
def _set_content_type(self, format=None):
|
|
||||||
"""
|
|
||||||
Set the mime-type to either 'xml' or 'json'.
|
|
||||||
|
|
||||||
:param format: format to be set.
|
|
||||||
:return: mime-type string
|
|
||||||
"""
|
|
||||||
if not format:
|
|
||||||
format = self.format
|
|
||||||
return "application/%s" % format
|
|
||||||
|
|
||||||
def _delete(self, action, body=None, headers=None):
|
|
||||||
return self._do_request("DELETE", action, body=body,
|
|
||||||
headers=headers)
|
|
||||||
|
|
||||||
def _get(self, action, body=None, headers=None):
|
|
||||||
return self._do_request("GET", action, body=body,
|
|
||||||
headers=headers)
|
|
||||||
|
|
||||||
def _post(self, action, body=None, headers=None):
|
|
||||||
return self._do_request("POST", action, body=body,
|
|
||||||
headers=headers)
|
|
||||||
|
|
||||||
def _put(self, action, body=None, headers=None):
|
|
||||||
return self._do_request("PUT", action, body=body,
|
|
||||||
headers=headers)
|
|
||||||
|
|
||||||
def _get_vsm_hosts(self):
|
|
||||||
"""
|
|
||||||
Retrieve a list of VSM ip addresses.
|
|
||||||
|
|
||||||
:return: list of host ip addresses
|
|
||||||
"""
|
|
||||||
return [cr[c_const.CREDENTIAL_NAME] for cr in
|
|
||||||
network_db_v2.get_all_n1kv_credentials()]
|
|
||||||
|
|
||||||
def _get_auth_header(self, host_ip):
|
|
||||||
"""
|
|
||||||
Retrieve header with auth info for the VSM.
|
|
||||||
|
|
||||||
:param host_ip: IP address of the VSM
|
|
||||||
:return: authorization header dict
|
|
||||||
"""
|
|
||||||
username = c_cred.Store.get_username(host_ip)
|
|
||||||
password = c_cred.Store.get_password(host_ip)
|
|
||||||
auth = base64.encodestring("%s:%s" % (username, password)).rstrip()
|
|
||||||
header = {"Authorization": "Basic %s" % auth}
|
|
||||||
return header
|
|
||||||
|
|
||||||
def get_clusters(self):
|
|
||||||
"""Fetches a list of all vxlan gateway clusters."""
|
|
||||||
return self._get(self.clusters_path)
|
|
||||||
|
|
||||||
def create_encapsulation_profile(self, encap):
|
|
||||||
"""
|
|
||||||
Create an encapsulation profile on VSM.
|
|
||||||
|
|
||||||
:param encap: encapsulation dict
|
|
||||||
"""
|
|
||||||
body = {'name': encap['name'],
|
|
||||||
'addMappings': encap['add_segment_list'],
|
|
||||||
'delMappings': encap['del_segment_list']}
|
|
||||||
return self._post(self.encap_profiles_path,
|
|
||||||
body=body)
|
|
||||||
|
|
||||||
def update_encapsulation_profile(self, context, profile_name, body):
|
|
||||||
"""
|
|
||||||
Adds a vlan to bridge-domain mapping to an encapsulation profile.
|
|
||||||
|
|
||||||
:param profile_name: Name of the encapsulation profile
|
|
||||||
:param body: mapping dictionary
|
|
||||||
"""
|
|
||||||
return self._post(self.encap_profile_path
|
|
||||||
% profile_name, body=body)
|
|
||||||
|
|
||||||
def delete_encapsulation_profile(self, name):
|
|
||||||
"""
|
|
||||||
Delete an encapsulation profile on VSM.
|
|
||||||
|
|
||||||
:param name: name of the encapsulation profile to be deleted
|
|
||||||
"""
|
|
||||||
return self._delete(self.encap_profile_path % name)
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,176 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import webob.exc as wexc
|
|
||||||
|
|
||||||
from neutron.api import extensions as neutron_extensions
|
|
||||||
from neutron.api.v2 import base
|
|
||||||
from neutron.db import db_base_plugin_v2
|
|
||||||
from neutron.openstack.common import importutils
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as cexc
|
|
||||||
from neutron.plugins.cisco.common import config
|
|
||||||
from neutron.plugins.cisco.db import network_db_v2 as cdb
|
|
||||||
from neutron.plugins.cisco import extensions
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PluginV2(db_base_plugin_v2.NeutronDbPluginV2):
|
|
||||||
"""Meta-Plugin with v2 API support for multiple sub-plugins."""
|
|
||||||
_supported_extension_aliases = ["credential", "Cisco qos"]
|
|
||||||
_methods_to_delegate = ['create_network',
|
|
||||||
'delete_network', 'update_network', 'get_network',
|
|
||||||
'get_networks',
|
|
||||||
'create_port', 'delete_port',
|
|
||||||
'update_port', 'get_port', 'get_ports',
|
|
||||||
'create_subnet',
|
|
||||||
'delete_subnet', 'update_subnet',
|
|
||||||
'get_subnet', 'get_subnets', ]
|
|
||||||
|
|
||||||
CISCO_FAULT_MAP = {
|
|
||||||
cexc.CredentialAlreadyExists: wexc.HTTPBadRequest,
|
|
||||||
cexc.CredentialNameNotFound: wexc.HTTPNotFound,
|
|
||||||
cexc.CredentialNotFound: wexc.HTTPNotFound,
|
|
||||||
cexc.NetworkSegmentIDNotFound: wexc.HTTPNotFound,
|
|
||||||
cexc.NetworkVlanBindingAlreadyExists: wexc.HTTPBadRequest,
|
|
||||||
cexc.NexusComputeHostNotConfigured: wexc.HTTPNotFound,
|
|
||||||
cexc.NexusConfigFailed: wexc.HTTPBadRequest,
|
|
||||||
cexc.NexusConnectFailed: wexc.HTTPServiceUnavailable,
|
|
||||||
cexc.NexusPortBindingNotFound: wexc.HTTPNotFound,
|
|
||||||
cexc.NoMoreNics: wexc.HTTPBadRequest,
|
|
||||||
cexc.PortIdForNexusSvi: wexc.HTTPBadRequest,
|
|
||||||
cexc.PortVnicBindingAlreadyExists: wexc.HTTPBadRequest,
|
|
||||||
cexc.PortVnicNotFound: wexc.HTTPNotFound,
|
|
||||||
cexc.QosNameAlreadyExists: wexc.HTTPBadRequest,
|
|
||||||
cexc.QosNotFound: wexc.HTTPNotFound,
|
|
||||||
cexc.SubnetNotSpecified: wexc.HTTPBadRequest,
|
|
||||||
cexc.VlanIDNotAvailable: wexc.HTTPNotFound,
|
|
||||||
cexc.VlanIDNotFound: wexc.HTTPNotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_extension_aliases(self):
|
|
||||||
if not hasattr(self, '_aliases'):
|
|
||||||
aliases = self._supported_extension_aliases[:]
|
|
||||||
if hasattr(self._model, "supported_extension_aliases"):
|
|
||||||
aliases.extend(self._model.supported_extension_aliases)
|
|
||||||
self._aliases = aliases
|
|
||||||
return self._aliases
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Load the model class."""
|
|
||||||
self._model_name = config.CISCO.model_class
|
|
||||||
self._model = importutils.import_object(self._model_name)
|
|
||||||
native_bulk_attr_name = ("_%s__native_bulk_support"
|
|
||||||
% self._model.__class__.__name__)
|
|
||||||
self.__native_bulk_support = getattr(self._model,
|
|
||||||
native_bulk_attr_name, False)
|
|
||||||
|
|
||||||
neutron_extensions.append_api_extensions_path(extensions.__path__)
|
|
||||||
|
|
||||||
# Extend the fault map
|
|
||||||
self._extend_fault_map()
|
|
||||||
|
|
||||||
LOG.debug(_("Plugin initialization complete"))
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
|
||||||
"""Delegate core API calls to the model class.
|
|
||||||
|
|
||||||
Core API calls are delegated directly to the configured model class.
|
|
||||||
Note: Bulking calls will be handled by this class, and turned into
|
|
||||||
non-bulking calls to be considered for delegation.
|
|
||||||
"""
|
|
||||||
methods = object.__getattribute__(self, "_methods_to_delegate")
|
|
||||||
if name in methods:
|
|
||||||
return getattr(object.__getattribute__(self, "_model"),
|
|
||||||
name)
|
|
||||||
else:
|
|
||||||
return object.__getattribute__(self, name)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
"""Delegate calls to the extensions.
|
|
||||||
|
|
||||||
This delegates the calls to the extensions explicitly implemented by
|
|
||||||
the model.
|
|
||||||
"""
|
|
||||||
if hasattr(self._model, name):
|
|
||||||
return getattr(self._model, name)
|
|
||||||
else:
|
|
||||||
# Must make sure we re-raise the error that led us here, since
|
|
||||||
# otherwise getattr() and even hasattr() doesn't work correctly.
|
|
||||||
raise AttributeError(
|
|
||||||
_("'%(model)s' object has no attribute '%(name)s'") %
|
|
||||||
{'model': self._model_name, 'name': name})
|
|
||||||
|
|
||||||
def _extend_fault_map(self):
|
|
||||||
"""Extend the Neutron Fault Map for Cisco exceptions.
|
|
||||||
|
|
||||||
Map exceptions which are specific to the Cisco Plugin
|
|
||||||
to standard HTTP exceptions.
|
|
||||||
|
|
||||||
"""
|
|
||||||
base.FAULT_MAP.update(self.CISCO_FAULT_MAP)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Extension API implementation
|
|
||||||
"""
|
|
||||||
def get_all_qoss(self, tenant_id):
|
|
||||||
"""Get all QoS levels."""
|
|
||||||
LOG.debug(_("get_all_qoss() called"))
|
|
||||||
qoslist = cdb.get_all_qoss(tenant_id)
|
|
||||||
return qoslist
|
|
||||||
|
|
||||||
def get_qos_details(self, tenant_id, qos_id):
|
|
||||||
"""Get QoS Details."""
|
|
||||||
LOG.debug(_("get_qos_details() called"))
|
|
||||||
return cdb.get_qos(tenant_id, qos_id)
|
|
||||||
|
|
||||||
def create_qos(self, tenant_id, qos_name, qos_desc):
|
|
||||||
"""Create a QoS level."""
|
|
||||||
LOG.debug(_("create_qos() called"))
|
|
||||||
qos = cdb.add_qos(tenant_id, qos_name, str(qos_desc))
|
|
||||||
return qos
|
|
||||||
|
|
||||||
def delete_qos(self, tenant_id, qos_id):
|
|
||||||
"""Delete a QoS level."""
|
|
||||||
LOG.debug(_("delete_qos() called"))
|
|
||||||
return cdb.remove_qos(tenant_id, qos_id)
|
|
||||||
|
|
||||||
def rename_qos(self, tenant_id, qos_id, new_name):
|
|
||||||
"""Rename QoS level."""
|
|
||||||
LOG.debug(_("rename_qos() called"))
|
|
||||||
return cdb.update_qos(tenant_id, qos_id, new_name)
|
|
||||||
|
|
||||||
def get_all_credentials(self):
|
|
||||||
"""Get all credentials."""
|
|
||||||
LOG.debug(_("get_all_credentials() called"))
|
|
||||||
credential_list = cdb.get_all_credentials()
|
|
||||||
return credential_list
|
|
||||||
|
|
||||||
def get_credential_details(self, credential_id):
|
|
||||||
"""Get a particular credential."""
|
|
||||||
LOG.debug(_("get_credential_details() called"))
|
|
||||||
return cdb.get_credential(credential_id)
|
|
||||||
|
|
||||||
def rename_credential(self, credential_id, new_name, new_password):
|
|
||||||
"""Rename the particular credential resource."""
|
|
||||||
LOG.debug(_("rename_credential() called"))
|
|
||||||
return cdb.update_credential(credential_id, new_name,
|
|
||||||
new_password=new_password)
|
|
|
@ -1,21 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
# @author: Edgar Magana, Cisco Systems, Inc.
|
|
||||||
"""
|
|
||||||
Init module for Nexus Driver
|
|
||||||
"""
|
|
|
@ -1,196 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Debojyoti Dutta, Cisco Systems, Inc.
|
|
||||||
# @author: Edgar Magana, Cisco Systems Inc.
|
|
||||||
#
|
|
||||||
"""
|
|
||||||
Implements a Nexus-OS NETCONF over SSHv2 API Client
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from ncclient import manager
|
|
||||||
|
|
||||||
from neutron.openstack.common import excutils
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants as const
|
|
||||||
from neutron.plugins.cisco.common import cisco_credentials_v2 as cred
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as cexc
|
|
||||||
from neutron.plugins.cisco.common import config as conf
|
|
||||||
from neutron.plugins.cisco.db import nexus_db_v2
|
|
||||||
from neutron.plugins.cisco.nexus import cisco_nexus_snippets as snipp
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CiscoNEXUSDriver():
|
|
||||||
"""Nexus Driver Main Class."""
|
|
||||||
def __init__(self):
|
|
||||||
cisco_switches = conf.get_device_dictionary()
|
|
||||||
self.nexus_switches = dict(((key[1], key[2]), val)
|
|
||||||
for key, val in cisco_switches.items()
|
|
||||||
if key[0] == 'NEXUS_SWITCH')
|
|
||||||
self.credentials = {}
|
|
||||||
self.connections = {}
|
|
||||||
|
|
||||||
def _edit_config(self, nexus_host, target='running', config='',
|
|
||||||
allowed_exc_strs=None):
|
|
||||||
"""Modify switch config for a target config type.
|
|
||||||
|
|
||||||
:param nexus_host: IP address of switch to configure
|
|
||||||
:param target: Target config type
|
|
||||||
:param config: Configuration string in XML format
|
|
||||||
:param allowed_exc_strs: Exceptions which have any of these strings
|
|
||||||
as a subset of their exception message
|
|
||||||
(str(exception)) can be ignored
|
|
||||||
|
|
||||||
:raises: NexusConfigFailed
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not allowed_exc_strs:
|
|
||||||
allowed_exc_strs = []
|
|
||||||
mgr = self.nxos_connect(nexus_host)
|
|
||||||
try:
|
|
||||||
mgr.edit_config(target, config=config)
|
|
||||||
except Exception as e:
|
|
||||||
for exc_str in allowed_exc_strs:
|
|
||||||
if exc_str in str(e):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Raise a Neutron exception. Include a description of
|
|
||||||
# the original ncclient exception. No need to preserve T/B
|
|
||||||
raise cexc.NexusConfigFailed(config=config, exc=e)
|
|
||||||
|
|
||||||
def get_credential(self, nexus_ip):
|
|
||||||
if nexus_ip not in self.credentials:
|
|
||||||
nexus_username = cred.Store.get_username(nexus_ip)
|
|
||||||
nexus_password = cred.Store.get_password(nexus_ip)
|
|
||||||
self.credentials[nexus_ip] = {
|
|
||||||
const.USERNAME: nexus_username,
|
|
||||||
const.PASSWORD: nexus_password
|
|
||||||
}
|
|
||||||
return self.credentials[nexus_ip]
|
|
||||||
|
|
||||||
def nxos_connect(self, nexus_host):
|
|
||||||
"""Make SSH connection to the Nexus Switch."""
|
|
||||||
if getattr(self.connections.get(nexus_host), 'connected', None):
|
|
||||||
return self.connections[nexus_host]
|
|
||||||
|
|
||||||
nexus_ssh_port = int(self.nexus_switches[nexus_host, 'ssh_port'])
|
|
||||||
nexus_creds = self.get_credential(nexus_host)
|
|
||||||
nexus_user = nexus_creds[const.USERNAME]
|
|
||||||
nexus_password = nexus_creds[const.PASSWORD]
|
|
||||||
try:
|
|
||||||
man = manager.connect(host=nexus_host,
|
|
||||||
port=nexus_ssh_port,
|
|
||||||
username=nexus_user,
|
|
||||||
password=nexus_password)
|
|
||||||
self.connections[nexus_host] = man
|
|
||||||
except Exception as e:
|
|
||||||
# Raise a Neutron exception. Include a description of
|
|
||||||
# the original ncclient exception. No need to preserve T/B.
|
|
||||||
raise cexc.NexusConnectFailed(nexus_host=nexus_host, exc=e)
|
|
||||||
|
|
||||||
return self.connections[nexus_host]
|
|
||||||
|
|
||||||
def create_xml_snippet(self, cutomized_config):
|
|
||||||
"""Create XML snippet.
|
|
||||||
|
|
||||||
Creates the Proper XML structure for the Nexus Switch Configuration.
|
|
||||||
"""
|
|
||||||
conf_xml_snippet = snipp.EXEC_CONF_SNIPPET % (cutomized_config)
|
|
||||||
return conf_xml_snippet
|
|
||||||
|
|
||||||
def create_vlan(self, nexus_host, vlanid, vlanname):
|
|
||||||
"""Create a VLAN on Nexus Switch given the VLAN ID and Name."""
|
|
||||||
confstr = self.create_xml_snippet(
|
|
||||||
snipp.CMD_VLAN_CONF_SNIPPET % (vlanid, vlanname))
|
|
||||||
self._edit_config(nexus_host, target='running', config=confstr)
|
|
||||||
|
|
||||||
# Enable VLAN active and no-shutdown states. Some versions of
|
|
||||||
# Nexus switch do not allow state changes for the extended VLAN
|
|
||||||
# range (1006-4094), but these errors can be ignored (default
|
|
||||||
# values are appropriate).
|
|
||||||
state_config = [snipp.CMD_VLAN_ACTIVE_SNIPPET,
|
|
||||||
snipp.CMD_VLAN_NO_SHUTDOWN_SNIPPET]
|
|
||||||
for snippet in state_config:
|
|
||||||
try:
|
|
||||||
confstr = self.create_xml_snippet(snippet % vlanid)
|
|
||||||
self._edit_config(
|
|
||||||
nexus_host,
|
|
||||||
target='running',
|
|
||||||
config=confstr,
|
|
||||||
allowed_exc_strs=["Can't modify state for extended",
|
|
||||||
"Command is only allowed on VLAN"])
|
|
||||||
except cexc.NexusConfigFailed:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
self.delete_vlan(nexus_host, vlanid)
|
|
||||||
|
|
||||||
def delete_vlan(self, nexus_host, vlanid):
|
|
||||||
"""Delete a VLAN on Nexus Switch given the VLAN ID."""
|
|
||||||
confstr = snipp.CMD_NO_VLAN_CONF_SNIPPET % vlanid
|
|
||||||
confstr = self.create_xml_snippet(confstr)
|
|
||||||
self._edit_config(nexus_host, target='running', config=confstr)
|
|
||||||
|
|
||||||
def enable_vlan_on_trunk_int(self, nexus_host, vlanid, etype, interface):
|
|
||||||
"""Enable a VLAN on a trunk interface."""
|
|
||||||
# If one or more VLANs are already configured on this interface,
|
|
||||||
# include the 'add' keyword.
|
|
||||||
if nexus_db_v2.get_port_switch_bindings('%s:%s' % (etype, interface),
|
|
||||||
nexus_host):
|
|
||||||
snippet = snipp.CMD_INT_VLAN_ADD_SNIPPET
|
|
||||||
else:
|
|
||||||
snippet = snipp.CMD_INT_VLAN_SNIPPET
|
|
||||||
confstr = snippet % (etype, interface, vlanid, etype)
|
|
||||||
confstr = self.create_xml_snippet(confstr)
|
|
||||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
|
||||||
self._edit_config(nexus_host, target='running', config=confstr)
|
|
||||||
|
|
||||||
def disable_vlan_on_trunk_int(self, nexus_host, vlanid, etype, interface):
|
|
||||||
"""Disable a VLAN on a trunk interface."""
|
|
||||||
confstr = snipp.CMD_NO_VLAN_INT_SNIPPET % (etype, interface,
|
|
||||||
vlanid, etype)
|
|
||||||
confstr = self.create_xml_snippet(confstr)
|
|
||||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
|
||||||
self._edit_config(nexus_host, target='running', config=confstr)
|
|
||||||
|
|
||||||
def create_and_trunk_vlan(self, nexus_host, vlan_id, vlan_name,
|
|
||||||
etype, nexus_port):
|
|
||||||
"""Create VLAN and trunk it on the specified ports."""
|
|
||||||
self.create_vlan(nexus_host, vlan_id, vlan_name)
|
|
||||||
LOG.debug(_("NexusDriver created VLAN: %s"), vlan_id)
|
|
||||||
if nexus_port:
|
|
||||||
self.enable_vlan_on_trunk_int(nexus_host, vlan_id,
|
|
||||||
etype, nexus_port)
|
|
||||||
|
|
||||||
def delete_and_untrunk_vlan(self, nexus_host, vlan_id, etype, nexus_port):
|
|
||||||
"""Delete VLAN and untrunk it from the specified ports."""
|
|
||||||
self.delete_vlan(nexus_host, vlan_id)
|
|
||||||
if nexus_port:
|
|
||||||
self.disable_vlan_on_trunk_int(nexus_host, vlan_id,
|
|
||||||
etype, nexus_port)
|
|
||||||
|
|
||||||
def create_vlan_svi(self, nexus_host, vlan_id, gateway_ip):
|
|
||||||
confstr = snipp.CMD_VLAN_SVI_SNIPPET % (vlan_id, gateway_ip)
|
|
||||||
confstr = self.create_xml_snippet(confstr)
|
|
||||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
|
||||||
self._edit_config(nexus_host, target='running', config=confstr)
|
|
||||||
|
|
||||||
def delete_vlan_svi(self, nexus_host, vlan_id):
|
|
||||||
confstr = snipp.CMD_NO_VLAN_SVI_SNIPPET % vlan_id
|
|
||||||
confstr = self.create_xml_snippet(confstr)
|
|
||||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
|
||||||
self._edit_config(nexus_host, target='running', config=confstr)
|
|
|
@ -1,347 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
# @author: Edgar Magana, Cisco Systems, Inc.
|
|
||||||
# @author: Arvind Somya, Cisco Systems, Inc. (asomya@cisco.com)
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
PlugIn for Nexus OS driver
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from neutron.openstack.common import excutils
|
|
||||||
from neutron.openstack.common import importutils
|
|
||||||
from neutron.plugins.cisco.common import cisco_constants as const
|
|
||||||
from neutron.plugins.cisco.common import cisco_exceptions as cisco_exc
|
|
||||||
from neutron.plugins.cisco.common import config as conf
|
|
||||||
from neutron.plugins.cisco.db import network_db_v2 as cdb
|
|
||||||
from neutron.plugins.cisco.db import nexus_db_v2 as nxos_db
|
|
||||||
from neutron.plugins.cisco import l2device_plugin_base
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class NexusPlugin(l2device_plugin_base.L2DevicePluginBase):
|
|
||||||
"""Nexus PlugIn Main Class."""
|
|
||||||
_networks = {}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Extract configuration parameters from the configuration file."""
|
|
||||||
self._client = importutils.import_object(conf.CISCO.nexus_driver)
|
|
||||||
LOG.debug(_("Loaded driver %s"), conf.CISCO.nexus_driver)
|
|
||||||
self._nexus_switches = conf.get_device_dictionary()
|
|
||||||
|
|
||||||
def create_network(self, network, attachment):
|
|
||||||
"""Create or update a network when an attachment is changed.
|
|
||||||
|
|
||||||
This method is not invoked at the usual plugin create_network() time.
|
|
||||||
Instead, it is invoked on create/update port.
|
|
||||||
|
|
||||||
:param network: Network on which the port operation is happening
|
|
||||||
:param attachment: Details about the owner of the port
|
|
||||||
|
|
||||||
Create a VLAN in the appropriate switch/port, and configure the
|
|
||||||
appropriate interfaces for this VLAN.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:create_network() called"))
|
|
||||||
# Grab the switch IPs and ports for this host
|
|
||||||
host_connections = []
|
|
||||||
host = attachment['host_name']
|
|
||||||
for switch_type, switch_ip, attr in self._nexus_switches:
|
|
||||||
if str(attr) == str(host):
|
|
||||||
port = self._nexus_switches[switch_type, switch_ip, attr]
|
|
||||||
# Get ether type for port, assume an ethernet type
|
|
||||||
# if none specified.
|
|
||||||
if ':' in port:
|
|
||||||
etype, port_id = port.split(':')
|
|
||||||
else:
|
|
||||||
etype, port_id = 'ethernet', port
|
|
||||||
host_connections.append((switch_ip, etype, port_id))
|
|
||||||
if not host_connections:
|
|
||||||
raise cisco_exc.NexusComputeHostNotConfigured(host=host)
|
|
||||||
|
|
||||||
vlan_id = network[const.NET_VLAN_ID]
|
|
||||||
vlan_name = network[const.NET_VLAN_NAME]
|
|
||||||
auto_create = True
|
|
||||||
auto_trunk = True
|
|
||||||
if cdb.is_provider_vlan(vlan_id):
|
|
||||||
vlan_name = ''.join([conf.CISCO.provider_vlan_name_prefix,
|
|
||||||
str(vlan_id)])
|
|
||||||
auto_create = conf.CISCO.provider_vlan_auto_create
|
|
||||||
auto_trunk = conf.CISCO.provider_vlan_auto_trunk
|
|
||||||
|
|
||||||
# Check if this network is already in the DB
|
|
||||||
for switch_ip, etype, port_id in host_connections:
|
|
||||||
vlan_created = False
|
|
||||||
vlan_trunked = False
|
|
||||||
eport_id = '%s:%s' % (etype, port_id)
|
|
||||||
# Check for switch vlan bindings
|
|
||||||
try:
|
|
||||||
# This vlan has already been created on this switch
|
|
||||||
# via another operation, like SVI bindings.
|
|
||||||
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
|
||||||
vlan_created = True
|
|
||||||
auto_create = False
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
# No changes, proceed as normal
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
nxos_db.get_port_vlan_switch_binding(eport_id, vlan_id,
|
|
||||||
switch_ip)
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
if auto_create and auto_trunk:
|
|
||||||
# Create vlan and trunk vlan on the port
|
|
||||||
LOG.debug(_("Nexus: create & trunk vlan %s"), vlan_name)
|
|
||||||
self._client.create_and_trunk_vlan(
|
|
||||||
switch_ip, vlan_id, vlan_name, etype, port_id)
|
|
||||||
vlan_created = True
|
|
||||||
vlan_trunked = True
|
|
||||||
elif auto_create:
|
|
||||||
# Create vlan but do not trunk it on the port
|
|
||||||
LOG.debug(_("Nexus: create vlan %s"), vlan_name)
|
|
||||||
self._client.create_vlan(switch_ip, vlan_id, vlan_name)
|
|
||||||
vlan_created = True
|
|
||||||
elif auto_trunk:
|
|
||||||
# Only trunk vlan on the port
|
|
||||||
LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
|
|
||||||
self._client.enable_vlan_on_trunk_int(
|
|
||||||
switch_ip, vlan_id, etype, port_id)
|
|
||||||
vlan_trunked = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
instance = attachment[const.INSTANCE_ID]
|
|
||||||
nxos_db.add_nexusport_binding(eport_id, str(vlan_id),
|
|
||||||
switch_ip, instance)
|
|
||||||
except Exception:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
# Add binding failed, roll back any vlan creation/enabling
|
|
||||||
if vlan_created and vlan_trunked:
|
|
||||||
LOG.debug(_("Nexus: delete & untrunk vlan %s"),
|
|
||||||
vlan_name)
|
|
||||||
self._client.delete_and_untrunk_vlan(switch_ip,
|
|
||||||
vlan_id,
|
|
||||||
etype, port_id)
|
|
||||||
elif vlan_created:
|
|
||||||
LOG.debug(_("Nexus: delete vlan %s"), vlan_name)
|
|
||||||
self._client.delete_vlan(switch_ip, vlan_id)
|
|
||||||
elif vlan_trunked:
|
|
||||||
LOG.debug(_("Nexus: untrunk vlan %s"), vlan_name)
|
|
||||||
self._client.disable_vlan_on_trunk_int(switch_ip,
|
|
||||||
vlan_id,
|
|
||||||
etype,
|
|
||||||
port_id)
|
|
||||||
|
|
||||||
net_id = network[const.NET_ID]
|
|
||||||
new_net_dict = {const.NET_ID: net_id,
|
|
||||||
const.NET_NAME: network[const.NET_NAME],
|
|
||||||
const.NET_PORTS: {},
|
|
||||||
const.NET_VLAN_NAME: vlan_name,
|
|
||||||
const.NET_VLAN_ID: vlan_id}
|
|
||||||
self._networks[net_id] = new_net_dict
|
|
||||||
return new_net_dict
|
|
||||||
|
|
||||||
def add_router_interface(self, vlan_name, vlan_id, subnet_id,
|
|
||||||
gateway_ip, router_id):
|
|
||||||
"""Create VLAN SVI on the Nexus switch."""
|
|
||||||
# Find a switch to create the SVI on
|
|
||||||
switch_ip = self._find_switch_for_svi()
|
|
||||||
if not switch_ip:
|
|
||||||
raise cisco_exc.NoNexusSviSwitch()
|
|
||||||
|
|
||||||
# Check if this vlan exists on the switch already
|
|
||||||
try:
|
|
||||||
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
# Create vlan and trunk vlan on the port
|
|
||||||
self._client.create_and_trunk_vlan(
|
|
||||||
switch_ip, vlan_id, vlan_name, etype=None, nexus_port=None)
|
|
||||||
# Check if a router interface has already been created
|
|
||||||
try:
|
|
||||||
nxos_db.get_nexusvm_bindings(vlan_id, router_id)
|
|
||||||
raise cisco_exc.SubnetInterfacePresent(subnet_id=subnet_id,
|
|
||||||
router_id=router_id)
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
self._client.create_vlan_svi(switch_ip, vlan_id, gateway_ip)
|
|
||||||
nxos_db.add_nexusport_binding('router', str(vlan_id),
|
|
||||||
switch_ip, router_id)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def remove_router_interface(self, vlan_id, router_id):
|
|
||||||
"""Remove VLAN SVI from the Nexus Switch."""
|
|
||||||
# Grab switch_ip from database
|
|
||||||
switch_ip = nxos_db.get_nexusvm_bindings(vlan_id,
|
|
||||||
router_id)[0].switch_ip
|
|
||||||
|
|
||||||
# Delete the SVI interface from the switch
|
|
||||||
self._client.delete_vlan_svi(switch_ip, vlan_id)
|
|
||||||
|
|
||||||
# Invoke delete_port to delete this row
|
|
||||||
# And delete vlan if required
|
|
||||||
return self.delete_port(router_id, vlan_id)
|
|
||||||
|
|
||||||
def _find_switch_for_svi(self):
|
|
||||||
"""Get a switch to create the SVI on."""
|
|
||||||
LOG.debug(_("Grabbing a switch to create SVI"))
|
|
||||||
nexus_switches = self._client.nexus_switches
|
|
||||||
if conf.CISCO.svi_round_robin:
|
|
||||||
LOG.debug(_("Using round robin to create SVI"))
|
|
||||||
switch_dict = dict(
|
|
||||||
(switch_ip, 0) for switch_ip, _ in nexus_switches)
|
|
||||||
try:
|
|
||||||
bindings = nxos_db.get_nexussvi_bindings()
|
|
||||||
# Build a switch dictionary with weights
|
|
||||||
for binding in bindings:
|
|
||||||
switch_ip = binding.switch_ip
|
|
||||||
if switch_ip not in switch_dict:
|
|
||||||
switch_dict[switch_ip] = 1
|
|
||||||
else:
|
|
||||||
switch_dict[switch_ip] += 1
|
|
||||||
# Search for the lowest value in the dict
|
|
||||||
if switch_dict:
|
|
||||||
switch_ip = min(switch_dict, key=switch_dict.get)
|
|
||||||
return switch_ip
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
LOG.debug(_("No round robin or zero weights, using first switch"))
|
|
||||||
# Return the first switch in the config
|
|
||||||
return conf.first_device_ip
|
|
||||||
|
|
||||||
def delete_network(self, tenant_id, net_id, **kwargs):
|
|
||||||
"""Delete network.
|
|
||||||
|
|
||||||
Not applicable to Nexus plugin. Defined here to satisfy abstract
|
|
||||||
method requirements.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:delete_network() called")) # pragma no cover
|
|
||||||
|
|
||||||
def update_network(self, tenant_id, net_id, **kwargs):
|
|
||||||
"""Update the properties of a particular Virtual Network.
|
|
||||||
|
|
||||||
Not applicable to Nexus plugin. Defined here to satisfy abstract
|
|
||||||
method requirements.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:update_network() called")) # pragma no cover
|
|
||||||
|
|
||||||
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
|
|
||||||
"""Create port.
|
|
||||||
|
|
||||||
Not applicable to Nexus plugin. Defined here to satisfy abstract
|
|
||||||
method requirements.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:create_port() called")) # pragma no cover
|
|
||||||
|
|
||||||
def delete_port(self, device_id, vlan_id):
|
|
||||||
"""Delete port.
|
|
||||||
|
|
||||||
Delete port bindings from the database and scan whether the network
|
|
||||||
is still required on the interfaces trunked.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:delete_port() called"))
|
|
||||||
# Delete DB row(s) for this port
|
|
||||||
try:
|
|
||||||
rows = nxos_db.get_nexusvm_bindings(vlan_id, device_id)
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
return
|
|
||||||
|
|
||||||
auto_delete = True
|
|
||||||
auto_untrunk = True
|
|
||||||
if cdb.is_provider_vlan(vlan_id):
|
|
||||||
auto_delete = conf.CISCO.provider_vlan_auto_create
|
|
||||||
auto_untrunk = conf.CISCO.provider_vlan_auto_trunk
|
|
||||||
LOG.debug(_("delete_network(): provider vlan %s"), vlan_id)
|
|
||||||
|
|
||||||
instance_id = False
|
|
||||||
for row in rows:
|
|
||||||
instance_id = row['instance_id']
|
|
||||||
switch_ip = row.switch_ip
|
|
||||||
etype, nexus_port = '', ''
|
|
||||||
if row['port_id'] == 'router':
|
|
||||||
etype, nexus_port = 'vlan', row['port_id']
|
|
||||||
auto_untrunk = False
|
|
||||||
else:
|
|
||||||
etype, nexus_port = row['port_id'].split(':')
|
|
||||||
|
|
||||||
nxos_db.remove_nexusport_binding(row.port_id, row.vlan_id,
|
|
||||||
row.switch_ip,
|
|
||||||
row.instance_id)
|
|
||||||
# Check whether there are any remaining instances using this
|
|
||||||
# vlan on this Nexus port.
|
|
||||||
try:
|
|
||||||
nxos_db.get_port_vlan_switch_binding(row.port_id,
|
|
||||||
row.vlan_id,
|
|
||||||
row.switch_ip)
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
try:
|
|
||||||
if nexus_port and auto_untrunk:
|
|
||||||
# Untrunk the vlan from this Nexus interface
|
|
||||||
self._client.disable_vlan_on_trunk_int(
|
|
||||||
switch_ip, row.vlan_id, etype, nexus_port)
|
|
||||||
|
|
||||||
# Check whether there are any remaining instances
|
|
||||||
# using this vlan on the Nexus switch.
|
|
||||||
if auto_delete:
|
|
||||||
try:
|
|
||||||
nxos_db.get_nexusvlan_binding(row.vlan_id,
|
|
||||||
row.switch_ip)
|
|
||||||
except cisco_exc.NexusPortBindingNotFound:
|
|
||||||
# Delete this vlan from this switch
|
|
||||||
self._client.delete_vlan(switch_ip, row.vlan_id)
|
|
||||||
except Exception:
|
|
||||||
# The delete vlan operation on the Nexus failed,
|
|
||||||
# so this delete_port request has failed. For
|
|
||||||
# consistency, roll back the Nexus database to what
|
|
||||||
# it was before this request.
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
nxos_db.add_nexusport_binding(row.port_id,
|
|
||||||
row.vlan_id,
|
|
||||||
row.switch_ip,
|
|
||||||
row.instance_id)
|
|
||||||
|
|
||||||
return instance_id
|
|
||||||
|
|
||||||
def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs):
|
|
||||||
"""Update port.
|
|
||||||
|
|
||||||
Not applicable to Nexus plugin. Defined here to satisfy abstract
|
|
||||||
method requirements.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:update_port() called")) # pragma no cover
|
|
||||||
|
|
||||||
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
|
|
||||||
**kwargs):
|
|
||||||
"""Plug interfaces.
|
|
||||||
|
|
||||||
Not applicable to Nexus plugin. Defined here to satisfy abstract
|
|
||||||
method requirements.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:plug_interface() called")) # pragma no cover
|
|
||||||
|
|
||||||
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
|
|
||||||
"""Unplug interface.
|
|
||||||
|
|
||||||
Not applicable to Nexus plugin. Defined here to satisfy abstract
|
|
||||||
method requirements.
|
|
||||||
"""
|
|
||||||
LOG.debug(_("NexusPlugin:unplug_interface() called")
|
|
||||||
) # pragma no cover
|
|
|
@ -1,180 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2011 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Edgar Magana, Cisco Systems, Inc.
|
|
||||||
# @author: Arvind Somya (asomya@cisco.com) Cisco Systems, Inc.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Nexus-OS XML-based configuration snippets
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# The following are standard strings, messages used to communicate with Nexus,
|
|
||||||
EXEC_CONF_SNIPPET = """
|
|
||||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
||||||
<configure>
|
|
||||||
<__XML__MODE__exec_configure>%s
|
|
||||||
</__XML__MODE__exec_configure>
|
|
||||||
</configure>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_VLAN_CONF_SNIPPET = """
|
|
||||||
<vlan>
|
|
||||||
<vlan-id-create-delete>
|
|
||||||
<__XML__PARAM_value>%s</__XML__PARAM_value>
|
|
||||||
<__XML__MODE_vlan>
|
|
||||||
<name>
|
|
||||||
<vlan-name>%s</vlan-name>
|
|
||||||
</name>
|
|
||||||
</__XML__MODE_vlan>
|
|
||||||
</vlan-id-create-delete>
|
|
||||||
</vlan>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_VLAN_ACTIVE_SNIPPET = """
|
|
||||||
<vlan>
|
|
||||||
<vlan-id-create-delete>
|
|
||||||
<__XML__PARAM_value>%s</__XML__PARAM_value>
|
|
||||||
<__XML__MODE_vlan>
|
|
||||||
<state>
|
|
||||||
<vstate>active</vstate>
|
|
||||||
</state>
|
|
||||||
</__XML__MODE_vlan>
|
|
||||||
</vlan-id-create-delete>
|
|
||||||
</vlan>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_VLAN_NO_SHUTDOWN_SNIPPET = """
|
|
||||||
<vlan>
|
|
||||||
<vlan-id-create-delete>
|
|
||||||
<__XML__PARAM_value>%s</__XML__PARAM_value>
|
|
||||||
<__XML__MODE_vlan>
|
|
||||||
<no>
|
|
||||||
<shutdown/>
|
|
||||||
</no>
|
|
||||||
</__XML__MODE_vlan>
|
|
||||||
</vlan-id-create-delete>
|
|
||||||
</vlan>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_NO_VLAN_CONF_SNIPPET = """
|
|
||||||
<no>
|
|
||||||
<vlan>
|
|
||||||
<vlan-id-create-delete>
|
|
||||||
<__XML__PARAM_value>%s</__XML__PARAM_value>
|
|
||||||
</vlan-id-create-delete>
|
|
||||||
</vlan>
|
|
||||||
</no>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_INT_VLAN_HEADER = """
|
|
||||||
<interface>
|
|
||||||
<%s>
|
|
||||||
<interface>%s</interface>
|
|
||||||
<__XML__MODE_if-ethernet-switch>
|
|
||||||
<switchport>
|
|
||||||
<trunk>
|
|
||||||
<allowed>
|
|
||||||
<vlan>"""
|
|
||||||
|
|
||||||
CMD_VLAN_ID = """
|
|
||||||
<vlan_id>%s</vlan_id>"""
|
|
||||||
|
|
||||||
CMD_VLAN_ADD_ID = """
|
|
||||||
<add>%s
|
|
||||||
</add>""" % CMD_VLAN_ID
|
|
||||||
|
|
||||||
CMD_INT_VLAN_TRAILER = """
|
|
||||||
</vlan>
|
|
||||||
</allowed>
|
|
||||||
</trunk>
|
|
||||||
</switchport>
|
|
||||||
</__XML__MODE_if-ethernet-switch>
|
|
||||||
</%s>
|
|
||||||
</interface>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_INT_VLAN_SNIPPET = (CMD_INT_VLAN_HEADER +
|
|
||||||
CMD_VLAN_ID +
|
|
||||||
CMD_INT_VLAN_TRAILER)
|
|
||||||
|
|
||||||
CMD_INT_VLAN_ADD_SNIPPET = (CMD_INT_VLAN_HEADER +
|
|
||||||
CMD_VLAN_ADD_ID +
|
|
||||||
CMD_INT_VLAN_TRAILER)
|
|
||||||
|
|
||||||
CMD_NO_VLAN_INT_SNIPPET = """
|
|
||||||
<interface>
|
|
||||||
<%s>
|
|
||||||
<interface>%s</interface>
|
|
||||||
<__XML__MODE_if-ethernet-switch>
|
|
||||||
<switchport></switchport>
|
|
||||||
<switchport>
|
|
||||||
<trunk>
|
|
||||||
<allowed>
|
|
||||||
<vlan>
|
|
||||||
<remove>
|
|
||||||
<vlan>%s</vlan>
|
|
||||||
</remove>
|
|
||||||
</vlan>
|
|
||||||
</allowed>
|
|
||||||
</trunk>
|
|
||||||
</switchport>
|
|
||||||
</__XML__MODE_if-ethernet-switch>
|
|
||||||
</%s>
|
|
||||||
</interface>
|
|
||||||
"""
|
|
||||||
|
|
||||||
FILTER_SHOW_VLAN_BRIEF_SNIPPET = """
|
|
||||||
<show xmlns="http://www.cisco.com/nxos:1.0:vlan_mgr_cli">
|
|
||||||
<vlan>
|
|
||||||
<brief/>
|
|
||||||
</vlan>
|
|
||||||
</show>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_VLAN_SVI_SNIPPET = """
|
|
||||||
<interface>
|
|
||||||
<vlan>
|
|
||||||
<vlan>%s</vlan>
|
|
||||||
<__XML__MODE_vlan>
|
|
||||||
<no>
|
|
||||||
<shutdown/>
|
|
||||||
</no>
|
|
||||||
<ip>
|
|
||||||
<address>
|
|
||||||
<address>%s</address>
|
|
||||||
</address>
|
|
||||||
</ip>
|
|
||||||
</__XML__MODE_vlan>
|
|
||||||
</vlan>
|
|
||||||
</interface>
|
|
||||||
"""
|
|
||||||
|
|
||||||
CMD_NO_VLAN_SVI_SNIPPET = """
|
|
||||||
<no>
|
|
||||||
<interface>
|
|
||||||
<vlan>
|
|
||||||
<vlan>%s</vlan>
|
|
||||||
</vlan>
|
|
||||||
</interface>
|
|
||||||
</no>
|
|
||||||
"""
|
|
|
@ -1,19 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012 OpenStack Foundation.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import __builtin__
|
|
||||||
setattr(__builtin__, '_', lambda x: x)
|
|
|
@ -1,101 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
#
|
|
||||||
# Copyright 2012 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
|
||||||
# @author: Rohit Agarwalla, Cisco Systems, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
class CiscoNEXUSFakeDriver():
|
|
||||||
"""Nexus Driver Fake Class."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user,
|
|
||||||
nexus_password):
|
|
||||||
"""Make the fake connection to the Nexus Switch."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_xml_snippet(self, cutomized_config):
|
|
||||||
"""Create XML snippet.
|
|
||||||
|
|
||||||
Creates the Proper XML structure for the Nexus Switch
|
|
||||||
Configuration.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def enable_vlan(self, mgr, vlanid, vlanname):
|
|
||||||
"""Create a VLAN on Nexus Switch given the VLAN ID and Name."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disable_vlan(self, mgr, vlanid):
|
|
||||||
"""Delete a VLAN on Nexus Switch given the VLAN ID."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disable_switch_port(self, mgr, interface):
|
|
||||||
"""Disable trunk mode an interface on Nexus Switch."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def enable_vlan_on_trunk_int(self, mgr, etype, interface, vlanid):
|
|
||||||
"""Enable vlan on trunk interface.
|
|
||||||
|
|
||||||
Enable trunk mode vlan access an interface on Nexus Switch given
|
|
||||||
VLANID.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disable_vlan_on_trunk_int(self, mgr, interface, vlanid):
|
|
||||||
"""Disables vlan in trunk interface.
|
|
||||||
|
|
||||||
Enables trunk mode vlan access an interface on Nexus Switch given
|
|
||||||
VLANID.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_vlan(self, vlan_name, vlan_id, nexus_host, nexus_user,
|
|
||||||
nexus_password, nexus_ports, nexus_ssh_port, vlan_ids):
|
|
||||||
"""Create VLAN and enable it on interface.
|
|
||||||
|
|
||||||
Creates a VLAN and Enable on trunk mode an interface on Nexus Switch
|
|
||||||
given the VLAN ID and Name and Interface Number.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password,
|
|
||||||
nexus_ports, nexus_ssh_port):
|
|
||||||
"""Delete VLAN.
|
|
||||||
|
|
||||||
Delete a VLAN and Disables trunk mode an interface on Nexus Switch
|
|
||||||
given the VLAN ID and Interface Number.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def build_vlans_cmd(self):
|
|
||||||
"""Build a string with all the VLANs on the same Switch."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def add_vlan_int(self, vlan_id, nexus_host, nexus_user, nexus_password,
|
|
||||||
nexus_ports, nexus_ssh_port, vlan_ids=None):
|
|
||||||
"""Add a vlan from interfaces on the Nexus switch given the VLAN ID."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def remove_vlan_int(self, vlan_id, nexus_host, nexus_user, nexus_password,
|
|
||||||
nexus_ports, nexus_ssh_port):
|
|
||||||
"""Remove vlan from interfaces.
|
|
||||||
|
|
||||||
Removes a vlan from interfaces on the Nexus switch given the VLAN ID.
|
|
||||||
"""
|
|
||||||
pass
|
|
|
@ -1,9 +0,0 @@
|
||||||
Embrane Neutron Plugin
|
|
||||||
|
|
||||||
This plugin interfaces OpenStack Neutron with Embrane's heleos platform, which
|
|
||||||
provides layer 3-7 network services for cloud environments.
|
|
||||||
|
|
||||||
L2 connectivity is leveraged by one of the supported existing plugins.
|
|
||||||
|
|
||||||
For more details on use, configuration and implementation please refer to:
|
|
||||||
http://wiki.openstack.org/wiki/Neutron/EmbraneNeutronPlugin
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,134 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from eventlet import greenthread
|
|
||||||
from eventlet import queue
|
|
||||||
from heleosapi import constants as h_con
|
|
||||||
from heleosapi import exceptions as h_exc
|
|
||||||
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.embrane.agent.operations import router_operations
|
|
||||||
from neutron.plugins.embrane.common import constants as p_con
|
|
||||||
from neutron.plugins.embrane.common import contexts as ctx
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
|
||||||
|
|
||||||
def __init__(self, plugin, async=True):
|
|
||||||
self._async = async
|
|
||||||
self._plugin = plugin
|
|
||||||
self.sync_items = dict()
|
|
||||||
|
|
||||||
def dispatch_l3(self, d_context, args=(), kwargs={}):
|
|
||||||
item = d_context.item
|
|
||||||
event = d_context.event
|
|
||||||
n_context = d_context.n_context
|
|
||||||
chain = d_context.chain
|
|
||||||
|
|
||||||
item_id = item["id"]
|
|
||||||
handlers = router_operations.handlers
|
|
||||||
if event in handlers:
|
|
||||||
for f in handlers[event]:
|
|
||||||
first_run = False
|
|
||||||
if item_id not in self.sync_items:
|
|
||||||
self.sync_items[item_id] = (queue.Queue(),)
|
|
||||||
first_run = True
|
|
||||||
self.sync_items[item_id][0].put(
|
|
||||||
ctx.OperationContext(event, n_context, item, chain, f,
|
|
||||||
args, kwargs))
|
|
||||||
t = None
|
|
||||||
if first_run:
|
|
||||||
t = greenthread.spawn(self._consume_l3,
|
|
||||||
item_id,
|
|
||||||
self.sync_items[item_id][0],
|
|
||||||
self._plugin,
|
|
||||||
self._async)
|
|
||||||
self.sync_items[item_id] += (t,)
|
|
||||||
if not self._async:
|
|
||||||
t = self.sync_items[item_id][1]
|
|
||||||
t.wait()
|
|
||||||
|
|
||||||
def _consume_l3(self, sync_item, sync_queue, plugin, a_sync):
|
|
||||||
current_state = None
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# If the DVA is deleted, the thread (and the associated queue)
|
|
||||||
# can die as well
|
|
||||||
if current_state == p_con.Status.DELETED:
|
|
||||||
del self.sync_items[sync_item]
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
# If synchronous op, empty the queue as fast as possible
|
|
||||||
operation_context = sync_queue.get(
|
|
||||||
block=a_sync,
|
|
||||||
timeout=p_con.QUEUE_TIMEOUT)
|
|
||||||
except queue.Empty:
|
|
||||||
del self.sync_items[sync_item]
|
|
||||||
return
|
|
||||||
# Execute the preliminary operations
|
|
||||||
(operation_context.chain and
|
|
||||||
operation_context.chain.execute_all())
|
|
||||||
# Execute the main operation, a transient state is maintained
|
|
||||||
# so that the consumer can decide if it has
|
|
||||||
# to be burned to the DB
|
|
||||||
transient_state = None
|
|
||||||
try:
|
|
||||||
dva_state = operation_context.function(
|
|
||||||
plugin._esm_api,
|
|
||||||
operation_context.n_context.tenant_id,
|
|
||||||
operation_context.item,
|
|
||||||
*operation_context.args,
|
|
||||||
**operation_context.kwargs)
|
|
||||||
if dva_state == p_con.Status.DELETED:
|
|
||||||
transient_state = dva_state
|
|
||||||
else:
|
|
||||||
if not dva_state:
|
|
||||||
transient_state = p_con.Status.ERROR
|
|
||||||
elif dva_state == h_con.DvaState.POWER_ON:
|
|
||||||
transient_state = p_con.Status.ACTIVE
|
|
||||||
else:
|
|
||||||
transient_state = p_con.Status.READY
|
|
||||||
|
|
||||||
except (h_exc.PendingDva, h_exc.DvaNotFound,
|
|
||||||
h_exc.BrokenInterface, h_exc.DvaCreationFailed,
|
|
||||||
h_exc.DvaCreationPending, h_exc.BrokenDva,
|
|
||||||
h_exc.ConfigurationFailed) as ex:
|
|
||||||
LOG.warning(p_con.error_map[type(ex)] % ex.message)
|
|
||||||
transient_state = p_con.Status.ERROR
|
|
||||||
except h_exc.DvaDeleteFailed as ex:
|
|
||||||
LOG.warning(p_con.error_map[type(ex)] % ex.message)
|
|
||||||
transient_state = p_con.Status.DELETED
|
|
||||||
finally:
|
|
||||||
# if the returned transient state is None, no operations
|
|
||||||
# are required on the DVA status
|
|
||||||
if transient_state:
|
|
||||||
if transient_state == p_con.Status.DELETED:
|
|
||||||
current_state = plugin._delete_router(
|
|
||||||
operation_context.n_context,
|
|
||||||
operation_context.item["id"])
|
|
||||||
# Error state cannot be reverted
|
|
||||||
elif transient_state != p_con.Status.ERROR:
|
|
||||||
current_state = plugin._update_neutron_state(
|
|
||||||
operation_context.n_context,
|
|
||||||
operation_context.item,
|
|
||||||
transient_state)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_("Unhandled exception occurred"))
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,156 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from heleosapi import exceptions as h_exc
|
|
||||||
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.embrane.common import constants as p_con
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
handlers = dict()
|
|
||||||
|
|
||||||
|
|
||||||
def handler(event, handler):
|
|
||||||
def wrap(f):
|
|
||||||
if event not in handler.keys():
|
|
||||||
new_func_list = [f]
|
|
||||||
handler[event] = new_func_list
|
|
||||||
else:
|
|
||||||
handler[event].append(f)
|
|
||||||
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapped_f(*args, **kwargs):
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
return wrapped_f
|
|
||||||
return wrap
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.CREATE_ROUTER, handlers)
|
|
||||||
def _create_dva_and_assign_address(api, tenant_id, neutron_router,
|
|
||||||
flavor, utif_info=None,
|
|
||||||
ip_allocation_info=None):
|
|
||||||
"""Creates a new router, and assign the gateway interface if any."""
|
|
||||||
|
|
||||||
dva = api.create_router(tenant_id=tenant_id,
|
|
||||||
router_id=neutron_router["id"],
|
|
||||||
name=neutron_router["name"],
|
|
||||||
flavor=flavor,
|
|
||||||
up=neutron_router["admin_state_up"])
|
|
||||||
try:
|
|
||||||
if utif_info:
|
|
||||||
api.grow_interface(utif_info, neutron_router["admin_state_up"],
|
|
||||||
tenant_id, neutron_router["id"])
|
|
||||||
if ip_allocation_info:
|
|
||||||
dva = api.allocate_address(neutron_router["id"],
|
|
||||||
neutron_router["admin_state_up"],
|
|
||||||
ip_allocation_info)
|
|
||||||
except h_exc.PreliminaryOperationsFailed as ex:
|
|
||||||
raise h_exc.BrokenInterface(err_msg=ex.message)
|
|
||||||
|
|
||||||
state = api.extract_dva_state(dva)
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.UPDATE_ROUTER, handlers)
|
|
||||||
def _update_dva_and_assign_address(api, tenant_id, neutron_router,
|
|
||||||
utif_info=None, ip_allocation_info=None,
|
|
||||||
routes_info=[]):
|
|
||||||
name = neutron_router["name"]
|
|
||||||
up = neutron_router["admin_state_up"]
|
|
||||||
r_id = neutron_router["id"]
|
|
||||||
if ip_allocation_info or routes_info:
|
|
||||||
up = True
|
|
||||||
dva = api.update_dva(tenant_id=tenant_id, router_id=r_id, name=name,
|
|
||||||
up=up, utif_info=utif_info)
|
|
||||||
if ip_allocation_info:
|
|
||||||
api.allocate_address(r_id, up, ip_allocation_info)
|
|
||||||
|
|
||||||
if routes_info:
|
|
||||||
api.delete_extra_routes(r_id, up)
|
|
||||||
api.set_extra_routes(r_id, neutron_router["admin_state_up"],
|
|
||||||
routes_info)
|
|
||||||
|
|
||||||
return api.extract_dva_state(dva)
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.DELETE_ROUTER, handlers)
|
|
||||||
def _delete_dva(api, tenant_id, neutron_router):
|
|
||||||
try:
|
|
||||||
api.delete_dva(tenant_id, neutron_router["id"])
|
|
||||||
except h_exc.DvaNotFound:
|
|
||||||
LOG.warning(_("The router %s had no physical representation,"
|
|
||||||
"likely already deleted"), neutron_router["id"])
|
|
||||||
return p_con.Status.DELETED
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.GROW_ROUTER_IF, handlers)
|
|
||||||
def _grow_dva_iface_and_assign_address(api, tenant_id, neutron_router,
|
|
||||||
utif_info=None,
|
|
||||||
ip_allocation_info=None):
|
|
||||||
try:
|
|
||||||
dva = api.grow_interface(utif_info, neutron_router["admin_state_up"],
|
|
||||||
tenant_id, neutron_router["id"])
|
|
||||||
if ip_allocation_info:
|
|
||||||
dva = api.allocate_address(neutron_router["id"],
|
|
||||||
neutron_router["admin_state_up"],
|
|
||||||
ip_allocation_info)
|
|
||||||
except h_exc.PreliminaryOperationsFailed as ex:
|
|
||||||
raise h_exc.BrokenInterface(err_msg=ex.message)
|
|
||||||
|
|
||||||
state = api.extract_dva_state(dva)
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.SHRINK_ROUTER_IF, handlers)
|
|
||||||
def _shrink_dva_iface(api, tenant_id, neutron_router, port_id):
|
|
||||||
try:
|
|
||||||
dva = api.shrink_interface(tenant_id, neutron_router["id"],
|
|
||||||
neutron_router["admin_state_up"], port_id)
|
|
||||||
except h_exc.InterfaceNotFound:
|
|
||||||
LOG.warning(_("Interface %s not found in the heleos back-end,"
|
|
||||||
"likely already deleted"), port_id)
|
|
||||||
return (p_con.Status.ACTIVE if neutron_router["admin_state_up"] else
|
|
||||||
p_con.Status.READY)
|
|
||||||
except h_exc.PreliminaryOperationsFailed as ex:
|
|
||||||
raise h_exc.BrokenInterface(err_msg=ex.message)
|
|
||||||
state = api.extract_dva_state(dva)
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.SET_NAT_RULE, handlers)
|
|
||||||
def _create_nat_rule(api, tenant_id, neutron_router, nat_info=None):
|
|
||||||
|
|
||||||
dva = api.create_nat_entry(neutron_router["id"],
|
|
||||||
neutron_router["admin_state_up"], nat_info)
|
|
||||||
|
|
||||||
state = api.extract_dva_state(dva)
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
@handler(p_con.Events.RESET_NAT_RULE, handlers)
|
|
||||||
def _delete_nat_rule(api, tenant_id, neutron_router, floating_ip_id):
|
|
||||||
|
|
||||||
dva = api.remove_nat_entry(neutron_router["id"],
|
|
||||||
neutron_router["admin_state_up"],
|
|
||||||
floating_ip_id)
|
|
||||||
|
|
||||||
state = api.extract_dva_state(dva)
|
|
||||||
return state
|
|
|
@ -1,375 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from heleosapi import backend_operations as h_op
|
|
||||||
from heleosapi import constants as h_con
|
|
||||||
from heleosapi import exceptions as h_exc
|
|
||||||
from oslo.config import cfg
|
|
||||||
from sqlalchemy.orm import exc
|
|
||||||
|
|
||||||
from neutron.common import constants as l3_constants
|
|
||||||
from neutron.common import exceptions as neutron_exc
|
|
||||||
from neutron.db import extraroute_db
|
|
||||||
from neutron.db import l3_db
|
|
||||||
from neutron.db import models_v2
|
|
||||||
from neutron.extensions import l3
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.embrane.agent import dispatcher
|
|
||||||
from neutron.plugins.embrane.common import config # noqa
|
|
||||||
from neutron.plugins.embrane.common import constants as p_con
|
|
||||||
from neutron.plugins.embrane.common import contexts as embrane_ctx
|
|
||||||
from neutron.plugins.embrane.common import operation
|
|
||||||
from neutron.plugins.embrane.common import utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
conf = cfg.CONF.heleos
|
|
||||||
|
|
||||||
|
|
||||||
class EmbranePlugin(object):
|
|
||||||
"""Embrane Neutron plugin.
|
|
||||||
|
|
||||||
uses the heleos(c) platform and a support L2 plugin to leverage networking
|
|
||||||
in cloud environments.
|
|
||||||
|
|
||||||
"""
|
|
||||||
_l3super = extraroute_db.ExtraRoute_db_mixin
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _run_embrane_config(self):
|
|
||||||
# read configurations
|
|
||||||
config_esm_mgmt = conf.esm_mgmt
|
|
||||||
config_admin_username = conf.admin_username
|
|
||||||
config_admin_password = conf.admin_password
|
|
||||||
config_router_image_id = conf.router_image
|
|
||||||
config_security_zones = {h_con.SzType.IB: conf.inband_id,
|
|
||||||
h_con.SzType.OOB: conf.oob_id,
|
|
||||||
h_con.SzType.MGMT: conf.mgmt_id,
|
|
||||||
h_con.SzType.DUMMY: conf.dummy_utif_id}
|
|
||||||
config_resource_pool = conf.resource_pool_id
|
|
||||||
self._embrane_async = conf.async_requests
|
|
||||||
self._esm_api = h_op.BackendOperations(
|
|
||||||
esm_mgmt=config_esm_mgmt,
|
|
||||||
admin_username=config_admin_username,
|
|
||||||
admin_password=config_admin_password,
|
|
||||||
router_image_id=config_router_image_id,
|
|
||||||
security_zones=config_security_zones,
|
|
||||||
resource_pool=config_resource_pool)
|
|
||||||
self._dispatcher = dispatcher.Dispatcher(self, self._embrane_async)
|
|
||||||
|
|
||||||
def _make_router_dict(self, *args, **kwargs):
|
|
||||||
return self._l3super._make_router_dict(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def _delete_router(self, context, router_id):
|
|
||||||
self._l3super.delete_router(self, context, router_id)
|
|
||||||
|
|
||||||
def _update_db_router_state(self, context, neutron_router, dva_state):
|
|
||||||
if not dva_state:
|
|
||||||
new_state = p_con.Status.ERROR
|
|
||||||
elif dva_state == h_con.DvaState.POWER_ON:
|
|
||||||
new_state = p_con.Status.ACTIVE
|
|
||||||
else:
|
|
||||||
new_state = p_con.Status.READY
|
|
||||||
self._set_db_router_state(context, neutron_router, new_state)
|
|
||||||
return new_state
|
|
||||||
|
|
||||||
def _set_db_router_state(self, context, neutron_router, new_state):
|
|
||||||
return utils.set_db_item_state(context, neutron_router, new_state)
|
|
||||||
|
|
||||||
def _update_db_interfaces_state(self, context, neutron_router):
|
|
||||||
router_ports = self.get_ports(context,
|
|
||||||
{"device_id": [neutron_router["id"]]})
|
|
||||||
self._esm_api.update_ports_status(neutron_router["id"], router_ports)
|
|
||||||
for port in router_ports:
|
|
||||||
db_port = self._get_port(context, port["id"])
|
|
||||||
db_port["status"] = port["status"]
|
|
||||||
context.session.merge(db_port)
|
|
||||||
|
|
||||||
def _update_neutron_state(self, context, neutron_router, state):
|
|
||||||
try:
|
|
||||||
self._update_db_interfaces_state(context, neutron_router)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_("Unhandled exception occurred"))
|
|
||||||
return self._set_db_router_state(context, neutron_router, state)
|
|
||||||
|
|
||||||
def _retrieve_prefix_from_port(self, context, neutron_port):
|
|
||||||
subnet_id = neutron_port["fixed_ips"][0]["subnet_id"]
|
|
||||||
subnet = utils.retrieve_subnet(context, subnet_id)
|
|
||||||
prefix = subnet["cidr"].split("/")[1]
|
|
||||||
return prefix
|
|
||||||
|
|
||||||
# L3 extension
|
|
||||||
def create_router(self, context, router):
|
|
||||||
r = router["router"]
|
|
||||||
self._get_tenant_id_for_create(context, r)
|
|
||||||
db_router = self._l3super.create_router(self, context, router)
|
|
||||||
neutron_router = self._get_router(context, db_router['id'])
|
|
||||||
gw_port = neutron_router.gw_port
|
|
||||||
# For now, only small flavor is used
|
|
||||||
utif_info = (self._plugin_support.retrieve_utif_info(context,
|
|
||||||
gw_port)
|
|
||||||
if gw_port else None)
|
|
||||||
ip_allocation_info = (utils.retrieve_ip_allocation_info(context,
|
|
||||||
gw_port)
|
|
||||||
if gw_port else None)
|
|
||||||
neutron_router = self._l3super._get_router(self, context,
|
|
||||||
neutron_router["id"])
|
|
||||||
neutron_router["status"] = p_con.Status.CREATING
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.CREATE_ROUTER, neutron_router, context, None),
|
|
||||||
args=(h_con.Flavor.SMALL, utif_info, ip_allocation_info))
|
|
||||||
return self._make_router_dict(neutron_router)
|
|
||||||
|
|
||||||
def update_router(self, context, id, router):
|
|
||||||
db_router = self._l3super.update_router(self, context, id, router)
|
|
||||||
neutron_router = self._get_router(context, db_router['id'])
|
|
||||||
gw_port = neutron_router.gw_port
|
|
||||||
utif_info = (self._plugin_support.retrieve_utif_info(context,
|
|
||||||
gw_port)
|
|
||||||
if gw_port else None)
|
|
||||||
ip_allocation_info = (utils.retrieve_ip_allocation_info(context,
|
|
||||||
gw_port)
|
|
||||||
if gw_port else None)
|
|
||||||
|
|
||||||
routes_info = router["router"].get("routes")
|
|
||||||
|
|
||||||
neutron_router = self._l3super._get_router(self, context, id)
|
|
||||||
state_change = operation.Operation(
|
|
||||||
self._set_db_router_state,
|
|
||||||
args=(context, neutron_router, p_con.Status.UPDATING))
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.UPDATE_ROUTER, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(utif_info, ip_allocation_info, routes_info))
|
|
||||||
return self._make_router_dict(neutron_router)
|
|
||||||
|
|
||||||
def get_router(self, context, id, fields=None):
|
|
||||||
"""Ensures that id does exist in the ESM."""
|
|
||||||
neutron_router = self._get_router(context, id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if neutron_router["status"] != p_con.Status.CREATING:
|
|
||||||
self._esm_api.get_dva(id)
|
|
||||||
except h_exc.DvaNotFound:
|
|
||||||
|
|
||||||
LOG.error(_("The following routers have not physical match: %s"),
|
|
||||||
id)
|
|
||||||
self._set_db_router_state(context, neutron_router,
|
|
||||||
p_con.Status.ERROR)
|
|
||||||
|
|
||||||
LOG.debug(_("Requested router: %s"), neutron_router)
|
|
||||||
return self._make_router_dict(neutron_router, fields)
|
|
||||||
|
|
||||||
def get_routers(self, context, filters=None, fields=None, sorts=None,
|
|
||||||
limit=None, marker=None, page_reverse=False):
|
|
||||||
"""Retrieves the router list defined by the incoming filters."""
|
|
||||||
router_query = self._apply_filters_to_query(
|
|
||||||
self._model_query(context, l3_db.Router),
|
|
||||||
l3_db.Router, filters)
|
|
||||||
id_list = [x["id"] for x in router_query
|
|
||||||
if x["status"] != p_con.Status.CREATING]
|
|
||||||
try:
|
|
||||||
self._esm_api.get_dvas(id_list)
|
|
||||||
except h_exc.DvaNotFound:
|
|
||||||
LOG.error(_("The following routers have not physical match: %s"),
|
|
||||||
repr(id_list))
|
|
||||||
error_routers = []
|
|
||||||
for id in id_list:
|
|
||||||
try:
|
|
||||||
error_routers.append(self._get_router(context, id))
|
|
||||||
except l3.RouterNotFound:
|
|
||||||
pass
|
|
||||||
for error_router in error_routers:
|
|
||||||
self._set_db_router_state(context, error_router,
|
|
||||||
p_con.Status.ERROR)
|
|
||||||
return [self._make_router_dict(router, fields)
|
|
||||||
for router in router_query]
|
|
||||||
|
|
||||||
def delete_router(self, context, id):
|
|
||||||
"""Deletes the DVA with the specific router id."""
|
|
||||||
# Copy of the parent validation code, shouldn't the base modules
|
|
||||||
# provide functions for validating operations?
|
|
||||||
device_owner_router_intf = l3_constants.DEVICE_OWNER_ROUTER_INTF
|
|
||||||
fips = self.get_floatingips_count(context.elevated(),
|
|
||||||
filters={"router_id": [id]})
|
|
||||||
if fips:
|
|
||||||
raise l3.RouterInUse(router_id=id)
|
|
||||||
|
|
||||||
device_filter = {"device_id": [id],
|
|
||||||
"device_owner": [device_owner_router_intf]}
|
|
||||||
ports = self.get_ports_count(context.elevated(),
|
|
||||||
filters=device_filter)
|
|
||||||
if ports:
|
|
||||||
raise l3.RouterInUse(router_id=id)
|
|
||||||
neutron_router = self._get_router(context, id)
|
|
||||||
state_change = operation.Operation(self._set_db_router_state,
|
|
||||||
args=(context, neutron_router,
|
|
||||||
p_con.Status.DELETING))
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.DELETE_ROUTER, neutron_router, context,
|
|
||||||
state_change), args=())
|
|
||||||
LOG.debug(_("Deleting router=%s"), neutron_router)
|
|
||||||
return neutron_router
|
|
||||||
|
|
||||||
def add_router_interface(self, context, router_id, interface_info):
|
|
||||||
"""Grows DVA interface in the specified subnet."""
|
|
||||||
neutron_router = self._get_router(context, router_id)
|
|
||||||
rport_qry = context.session.query(models_v2.Port)
|
|
||||||
ports = rport_qry.filter_by(
|
|
||||||
device_id=router_id).all()
|
|
||||||
if len(ports) >= p_con.UTIF_LIMIT:
|
|
||||||
raise neutron_exc.BadRequest(
|
|
||||||
resource=router_id,
|
|
||||||
msg=("this router doesn't support more than "
|
|
||||||
+ str(p_con.UTIF_LIMIT) + " interfaces"))
|
|
||||||
neutron_router_iface = self._l3super.add_router_interface(
|
|
||||||
self, context, router_id, interface_info)
|
|
||||||
port = self._get_port(context, neutron_router_iface["port_id"])
|
|
||||||
utif_info = self._plugin_support.retrieve_utif_info(context, port)
|
|
||||||
ip_allocation_info = utils.retrieve_ip_allocation_info(context,
|
|
||||||
port)
|
|
||||||
state_change = operation.Operation(self._set_db_router_state,
|
|
||||||
args=(context, neutron_router,
|
|
||||||
p_con.Status.UPDATING))
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.GROW_ROUTER_IF, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(utif_info, ip_allocation_info))
|
|
||||||
return neutron_router_iface
|
|
||||||
|
|
||||||
def remove_router_interface(self, context, router_id, interface_info):
|
|
||||||
port_id = None
|
|
||||||
if "port_id" in interface_info:
|
|
||||||
port_id = interface_info["port_id"]
|
|
||||||
elif "subnet_id" in interface_info:
|
|
||||||
subnet_id = interface_info["subnet_id"]
|
|
||||||
subnet = utils.retrieve_subnet(context, subnet_id)
|
|
||||||
rport_qry = context.session.query(models_v2.Port)
|
|
||||||
ports = rport_qry.filter_by(
|
|
||||||
device_id=router_id,
|
|
||||||
device_owner=l3_constants.DEVICE_OWNER_ROUTER_INTF,
|
|
||||||
network_id=subnet["network_id"])
|
|
||||||
for p in ports:
|
|
||||||
if p["fixed_ips"][0]["subnet_id"] == subnet_id:
|
|
||||||
port_id = p["id"]
|
|
||||||
break
|
|
||||||
neutron_router = self._get_router(context, router_id)
|
|
||||||
self._l3super.remove_router_interface(self, context, router_id,
|
|
||||||
interface_info)
|
|
||||||
state_change = operation.Operation(self._set_db_router_state,
|
|
||||||
args=(context, neutron_router,
|
|
||||||
p_con.Status.UPDATING))
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.SHRINK_ROUTER_IF, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(port_id,))
|
|
||||||
|
|
||||||
def create_floatingip(self, context, floatingip):
|
|
||||||
result = self._l3super.create_floatingip(
|
|
||||||
self, context, floatingip)
|
|
||||||
|
|
||||||
if result["port_id"]:
|
|
||||||
neutron_router = self._get_router(context, result["router_id"])
|
|
||||||
db_fixed_port = self._get_port(context, result["port_id"])
|
|
||||||
fixed_prefix = self._retrieve_prefix_from_port(context,
|
|
||||||
db_fixed_port)
|
|
||||||
db_floating_port = neutron_router["gw_port"]
|
|
||||||
floating_prefix = self._retrieve_prefix_from_port(
|
|
||||||
context, db_floating_port)
|
|
||||||
nat_info = utils.retrieve_nat_info(context, result,
|
|
||||||
fixed_prefix,
|
|
||||||
floating_prefix,
|
|
||||||
neutron_router)
|
|
||||||
state_change = operation.Operation(
|
|
||||||
self._set_db_router_state,
|
|
||||||
args=(context, neutron_router, p_con.Status.UPDATING))
|
|
||||||
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.SET_NAT_RULE, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(nat_info,))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def update_floatingip(self, context, id, floatingip):
|
|
||||||
db_fip = self._l3super.get_floatingip(self, context, id)
|
|
||||||
result = self._l3super.update_floatingip(self, context, id,
|
|
||||||
floatingip)
|
|
||||||
|
|
||||||
if db_fip["port_id"] and db_fip["port_id"] != result["port_id"]:
|
|
||||||
neutron_router = self._get_router(context, db_fip["router_id"])
|
|
||||||
fip_id = db_fip["id"]
|
|
||||||
state_change = operation.Operation(
|
|
||||||
self._set_db_router_state,
|
|
||||||
args=(context, neutron_router, p_con.Status.UPDATING))
|
|
||||||
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.RESET_NAT_RULE, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(fip_id,))
|
|
||||||
if result["port_id"]:
|
|
||||||
neutron_router = self._get_router(context, result["router_id"])
|
|
||||||
db_fixed_port = self._get_port(context, result["port_id"])
|
|
||||||
fixed_prefix = self._retrieve_prefix_from_port(context,
|
|
||||||
db_fixed_port)
|
|
||||||
db_floating_port = neutron_router["gw_port"]
|
|
||||||
floating_prefix = self._retrieve_prefix_from_port(
|
|
||||||
context, db_floating_port)
|
|
||||||
nat_info = utils.retrieve_nat_info(context, result,
|
|
||||||
fixed_prefix,
|
|
||||||
floating_prefix,
|
|
||||||
neutron_router)
|
|
||||||
state_change = operation.Operation(
|
|
||||||
self._set_db_router_state,
|
|
||||||
args=(context, neutron_router, p_con.Status.UPDATING))
|
|
||||||
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.SET_NAT_RULE, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(nat_info,))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def disassociate_floatingips(self, context, port_id):
|
|
||||||
try:
|
|
||||||
fip_qry = context.session.query(l3_db.FloatingIP)
|
|
||||||
floating_ip = fip_qry.filter_by(fixed_port_id=port_id).one()
|
|
||||||
router_id = floating_ip["router_id"]
|
|
||||||
except exc.NoResultFound:
|
|
||||||
return
|
|
||||||
self._l3super.disassociate_floatingips(self, context, port_id)
|
|
||||||
if router_id:
|
|
||||||
neutron_router = self._get_router(context, router_id)
|
|
||||||
fip_id = floating_ip["id"]
|
|
||||||
state_change = operation.Operation(
|
|
||||||
self._set_db_router_state,
|
|
||||||
args=(context, neutron_router, p_con.Status.UPDATING))
|
|
||||||
|
|
||||||
self._dispatcher.dispatch_l3(
|
|
||||||
d_context=embrane_ctx.DispatcherContext(
|
|
||||||
p_con.Events.RESET_NAT_RULE, neutron_router, context,
|
|
||||||
state_change),
|
|
||||||
args=(fip_id,))
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,49 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
|
|
||||||
heleos_opts = [
|
|
||||||
cfg.StrOpt('esm_mgmt',
|
|
||||||
help=_('ESM management root address')),
|
|
||||||
cfg.StrOpt('admin_username', default='admin',
|
|
||||||
help=_('ESM admin username.')),
|
|
||||||
cfg.StrOpt('admin_password',
|
|
||||||
secret=True,
|
|
||||||
help=_('ESM admin password.')),
|
|
||||||
cfg.StrOpt('router_image',
|
|
||||||
help=_('Router image id (Embrane FW/VPN)')),
|
|
||||||
cfg.StrOpt('inband_id',
|
|
||||||
help=_('In band Security Zone id')),
|
|
||||||
cfg.StrOpt('oob_id',
|
|
||||||
help=_('Out of band Security Zone id')),
|
|
||||||
cfg.StrOpt('mgmt_id',
|
|
||||||
help=_('Management Security Zone id')),
|
|
||||||
cfg.StrOpt('dummy_utif_id',
|
|
||||||
help=_('Dummy user traffic Security Zone id')),
|
|
||||||
cfg.StrOpt('resource_pool_id', default='default',
|
|
||||||
help=_('Shared resource pool id')),
|
|
||||||
cfg.BoolOpt('async_requests', default=True,
|
|
||||||
help=_('Define if the requests have '
|
|
||||||
'run asynchronously or not')),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(heleos_opts, "heleos")
|
|
|
@ -1,72 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from heleosapi import exceptions as h_exc
|
|
||||||
|
|
||||||
from neutron.plugins.common import constants
|
|
||||||
|
|
||||||
|
|
||||||
# Router specific constants
|
|
||||||
UTIF_LIMIT = 7
|
|
||||||
QUEUE_TIMEOUT = 300
|
|
||||||
|
|
||||||
|
|
||||||
class Status:
|
|
||||||
# Transient
|
|
||||||
CREATING = constants.PENDING_CREATE
|
|
||||||
UPDATING = constants.PENDING_UPDATE
|
|
||||||
DELETING = constants.PENDING_DELETE
|
|
||||||
# Final
|
|
||||||
ACTIVE = constants.ACTIVE
|
|
||||||
ERROR = constants.ERROR
|
|
||||||
READY = constants.INACTIVE
|
|
||||||
DELETED = "DELETED" # not visible
|
|
||||||
|
|
||||||
|
|
||||||
class Events:
|
|
||||||
CREATE_ROUTER = "create_router"
|
|
||||||
UPDATE_ROUTER = "update_router"
|
|
||||||
DELETE_ROUTER = "delete_router"
|
|
||||||
GROW_ROUTER_IF = "grow_router_if"
|
|
||||||
SHRINK_ROUTER_IF = "shrink_router_if"
|
|
||||||
SET_NAT_RULE = "set_nat_rule"
|
|
||||||
RESET_NAT_RULE = "reset_nat_rule"
|
|
||||||
|
|
||||||
_DVA_PENDING_ERROR_MSG = _("Dva is pending for the following reason: %s")
|
|
||||||
_DVA_NOT_FOUNT_ERROR_MSG = _("Dva can't be found to execute the operation, "
|
|
||||||
"probably was cancelled through the heleos UI")
|
|
||||||
_DVA_BROKEN_ERROR_MSG = _("Dva seems to be broken for reason %s")
|
|
||||||
_DVA_BROKEN_INTERFACE_ERROR_MSG = _("Dva interface seems to be broken "
|
|
||||||
"for reason %s")
|
|
||||||
_DVA_CREATION_FAILED_ERROR_MSG = _("Dva creation failed reason %s")
|
|
||||||
_DVA_CREATION_PENDING_ERROR_MSG = _("Dva creation is in pending state "
|
|
||||||
"for reason %s")
|
|
||||||
_CFG_FAILED_ERROR_MSG = _("Dva configuration failed for reason %s")
|
|
||||||
_DVA_DEL_FAILED_ERROR_MSG = _("Failed to delete the backend "
|
|
||||||
"router for reason %s. Please remove "
|
|
||||||
"it manually through the heleos UI")
|
|
||||||
|
|
||||||
error_map = {h_exc.PendingDva: _DVA_PENDING_ERROR_MSG,
|
|
||||||
h_exc.DvaNotFound: _DVA_NOT_FOUNT_ERROR_MSG,
|
|
||||||
h_exc.BrokenDva: _DVA_BROKEN_ERROR_MSG,
|
|
||||||
h_exc.BrokenInterface: _DVA_BROKEN_INTERFACE_ERROR_MSG,
|
|
||||||
h_exc.DvaCreationFailed: _DVA_CREATION_FAILED_ERROR_MSG,
|
|
||||||
h_exc.DvaCreationPending: _DVA_CREATION_PENDING_ERROR_MSG,
|
|
||||||
h_exc.ConfigurationFailed: _CFG_FAILED_ERROR_MSG,
|
|
||||||
h_exc.DvaDeleteFailed: _DVA_DEL_FAILED_ERROR_MSG}
|
|
|
@ -1,40 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
class DispatcherContext(object):
|
|
||||||
|
|
||||||
def __init__(self, event, item, neutron_context, chain=None):
|
|
||||||
self.event = event
|
|
||||||
self.item = item
|
|
||||||
self.n_context = neutron_context
|
|
||||||
self.chain = chain
|
|
||||||
|
|
||||||
|
|
||||||
class OperationContext(DispatcherContext):
|
|
||||||
"""Operational context.
|
|
||||||
|
|
||||||
contains all the parameters needed to execute a status aware operation
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, event, context, item, chain, function, args, kwargs):
|
|
||||||
super(OperationContext, self).__init__(event, item, context, chain)
|
|
||||||
self.function = function
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
|
@ -1,28 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from neutron.common import exceptions as neutron_exec
|
|
||||||
|
|
||||||
|
|
||||||
class EmbranePluginException(neutron_exec.NeutronException):
|
|
||||||
message = _("An unexpected error occurred:%(err_msg)s")
|
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedException(EmbranePluginException):
|
|
||||||
message = _("%(err_msg)s")
|
|
|
@ -1,51 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
class Operation(object):
|
|
||||||
"""Defines a series of operations which shall be executed in order.
|
|
||||||
|
|
||||||
the operations expected are procedures, return values are discarded
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, procedure, args=(), kwargs={}, nextop=None):
|
|
||||||
self._procedure = procedure
|
|
||||||
self.args = args[:]
|
|
||||||
self.kwargs = dict(kwargs)
|
|
||||||
self.nextop = nextop
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
args = self.args
|
|
||||||
self._procedure(*args, **self.kwargs)
|
|
||||||
return self.nextop
|
|
||||||
|
|
||||||
def execute_all(self):
|
|
||||||
nextop = self.execute()
|
|
||||||
while nextop:
|
|
||||||
nextop = self.execute_all()
|
|
||||||
|
|
||||||
def has_next(self):
|
|
||||||
return self.nextop is not None
|
|
||||||
|
|
||||||
def add_bottom_operation(self, operation):
|
|
||||||
op = self
|
|
||||||
while op.has_next():
|
|
||||||
op = op.nextop
|
|
||||||
op.nextop = operation
|
|
|
@ -1,73 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from heleosapi import info as h_info
|
|
||||||
|
|
||||||
from neutron.common import constants
|
|
||||||
from neutron.db import models_v2
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def set_db_item_state(context, neutron_item, new_state):
|
|
||||||
with context.session.begin(subtransactions=True):
|
|
||||||
if neutron_item["status"] != new_state:
|
|
||||||
neutron_item["status"] = new_state
|
|
||||||
context.session.merge(neutron_item)
|
|
||||||
|
|
||||||
|
|
||||||
def retrieve_subnet(context, subnet_id):
|
|
||||||
return (context.session.query(
|
|
||||||
models_v2.Subnet).filter(models_v2.Subnet.id == subnet_id).one())
|
|
||||||
|
|
||||||
|
|
||||||
def retrieve_ip_allocation_info(context, neutron_port):
|
|
||||||
"""Retrieves ip allocation info for a specific port if any."""
|
|
||||||
|
|
||||||
try:
|
|
||||||
subnet_id = neutron_port["fixed_ips"][0]["subnet_id"]
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
LOG.info(_("No ip allocation set"))
|
|
||||||
return
|
|
||||||
subnet = retrieve_subnet(context, subnet_id)
|
|
||||||
allocated_ip = neutron_port["fixed_ips"][0]["ip_address"]
|
|
||||||
is_gw_port = (neutron_port["device_owner"] ==
|
|
||||||
constants.DEVICE_OWNER_ROUTER_GW)
|
|
||||||
gateway_ip = subnet["gateway_ip"]
|
|
||||||
|
|
||||||
ip_allocation_info = h_info.IpAllocationInfo(
|
|
||||||
is_gw=is_gw_port,
|
|
||||||
ip_version=subnet["ip_version"],
|
|
||||||
prefix=subnet["cidr"].split("/")[1],
|
|
||||||
ip_address=allocated_ip,
|
|
||||||
port_id=neutron_port["id"],
|
|
||||||
gateway_ip=gateway_ip)
|
|
||||||
|
|
||||||
return ip_allocation_info
|
|
||||||
|
|
||||||
|
|
||||||
def retrieve_nat_info(context, fip, fixed_prefix, floating_prefix, router):
|
|
||||||
nat_info = h_info.NatInfo(source_address=fip["floating_ip_address"],
|
|
||||||
source_prefix=floating_prefix,
|
|
||||||
destination_address=fip["fixed_ip_address"],
|
|
||||||
destination_prefix=fixed_prefix,
|
|
||||||
floating_ip_id=fip["id"],
|
|
||||||
fixed_port_id=fip["port_id"])
|
|
||||||
return nat_info
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,24 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from neutron.db import db_base_plugin_v2
|
|
||||||
|
|
||||||
|
|
||||||
class FakeL2Plugin(db_base_plugin_v2.NeutronDbPluginV2):
|
|
||||||
supported_extension_aliases = []
|
|
|
@ -1,45 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from heleosapi import info as h_info
|
|
||||||
|
|
||||||
from neutron.common import constants
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.embrane.l2base import support_base as base
|
|
||||||
|
|
||||||
|
|
||||||
class FakePluginSupport(base.SupportBase):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(FakePluginSupport, self).__init__()
|
|
||||||
|
|
||||||
def retrieve_utif_info(self, context, neutron_port):
|
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
|
||||||
network_id = neutron_port["network_id"]
|
|
||||||
network = plugin._get_network(context, network_id)
|
|
||||||
is_gw = (neutron_port["device_owner"] ==
|
|
||||||
constants.DEVICE_OWNER_ROUTER_GW)
|
|
||||||
result = h_info.UtifInfo(vlan=0,
|
|
||||||
network_name=network["name"],
|
|
||||||
network_id=network["id"],
|
|
||||||
is_gw=is_gw,
|
|
||||||
owner_tenant=network["tenant_id"],
|
|
||||||
port_id=neutron_port["id"],
|
|
||||||
mac_address=neutron_port["mac_address"])
|
|
||||||
return result
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,58 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from heleosapi import info as h_info
|
|
||||||
|
|
||||||
from neutron.common import constants
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.embrane.l2base import support_base as base
|
|
||||||
from neutron.plugins.embrane.l2base import support_exceptions as exc
|
|
||||||
from neutron.plugins.openvswitch import ovs_db_v2
|
|
||||||
|
|
||||||
|
|
||||||
class OpenvswitchSupport(base.SupportBase):
|
|
||||||
"""OpenVSwitch plugin support.
|
|
||||||
|
|
||||||
Obtains the informations needed to build the user security zones
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(OpenvswitchSupport, self).__init__()
|
|
||||||
|
|
||||||
def retrieve_utif_info(self, context, neutron_port):
|
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
|
||||||
session = context.session
|
|
||||||
network_id = neutron_port["network_id"]
|
|
||||||
network_binding = ovs_db_v2.get_network_binding(session, network_id)
|
|
||||||
if not network_binding["segmentation_id"]:
|
|
||||||
raise exc.UtifInfoError(
|
|
||||||
err_msg=_("No segmentation_id found for the network, "
|
|
||||||
"please be sure that tenant_network_type is vlan"))
|
|
||||||
network = plugin._get_network(context, network_id)
|
|
||||||
is_gw = (neutron_port["device_owner"] ==
|
|
||||||
constants.DEVICE_OWNER_ROUTER_GW)
|
|
||||||
result = h_info.UtifInfo(vlan=network_binding["segmentation_id"],
|
|
||||||
network_name=network["name"],
|
|
||||||
network_id=network["id"],
|
|
||||||
is_gw=is_gw,
|
|
||||||
owner_tenant=network["tenant_id"],
|
|
||||||
port_id=neutron_port["id"],
|
|
||||||
mac_address=neutron_port["mac_address"])
|
|
||||||
return result
|
|
|
@ -1,50 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
import abc
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class SupportBase(object):
|
|
||||||
"""abstract support class.
|
|
||||||
|
|
||||||
Defines the methods a plugin support should implement to be used as
|
|
||||||
the L2 base for Embrane plugin.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def retrieve_utif_info(self, context, neutron_port=None, network=None):
|
|
||||||
"""Retrieve specific network info.
|
|
||||||
|
|
||||||
each plugin support, querying its own DB, can collect all the
|
|
||||||
information needed by the ESM in order to create the
|
|
||||||
user traffic security zone.
|
|
||||||
|
|
||||||
:param interface_info: the foo parameter
|
|
||||||
:param context: neutron request context
|
|
||||||
:returns: heleosapi.info.UtifInfo -- specific network info
|
|
||||||
:raises: UtifInfoError
|
|
||||||
"""
|
|
|
@ -1,25 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from neutron.plugins.embrane.common import exceptions as embrane_exc
|
|
||||||
|
|
||||||
|
|
||||||
class UtifInfoError(embrane_exc.EmbranePluginException):
|
|
||||||
message = _("Cannot retrieve utif info for the following reason: "
|
|
||||||
"%(err_msg)s")
|
|
|
@ -1,18 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
|
@ -1,34 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from neutron.db import extraroute_db
|
|
||||||
from neutron.plugins.embrane import base_plugin as base
|
|
||||||
from neutron.plugins.embrane.l2base.fake import fake_l2_plugin as l2
|
|
||||||
from neutron.plugins.embrane.l2base.fake import fakeplugin_support as sup
|
|
||||||
|
|
||||||
|
|
||||||
class EmbraneFakePlugin(base.EmbranePlugin, extraroute_db.ExtraRoute_db_mixin,
|
|
||||||
l2.FakeL2Plugin):
|
|
||||||
_plugin_support = sup.FakePluginSupport()
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
'''First run plugin specific initialization, then Embrane's.'''
|
|
||||||
self.supported_extension_aliases += ["extraroute", "router"]
|
|
||||||
l2.FakeL2Plugin.__init__(self)
|
|
||||||
self._run_embrane_config()
|
|
|
@ -1,38 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Embrane, 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
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# @author: Ivar Lazzaro, Embrane, Inc.
|
|
||||||
|
|
||||||
from neutron.plugins.embrane import base_plugin as base
|
|
||||||
from neutron.plugins.embrane.l2base.openvswitch import openvswitch_support
|
|
||||||
from neutron.plugins.openvswitch import ovs_neutron_plugin as l2
|
|
||||||
|
|
||||||
|
|
||||||
class EmbraneOvsPlugin(base.EmbranePlugin, l2.OVSNeutronPluginV2):
|
|
||||||
'''EmbraneOvsPlugin.
|
|
||||||
|
|
||||||
This plugin uses OpenVSwitch specific L2 plugin for providing L2 networks
|
|
||||||
and the base EmbranePlugin for L3.
|
|
||||||
|
|
||||||
'''
|
|
||||||
_plugin_support = openvswitch_support.OpenvswitchSupport()
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
'''First run plugin specific initialization, then Embrane's.'''
|
|
||||||
self._supported_extension_aliases.remove("l3_agent_scheduler")
|
|
||||||
l2.OVSNeutronPluginV2.__init__(self)
|
|
||||||
self._run_embrane_config()
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
|
@ -1,16 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
|
@ -1,475 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
#Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
#Copyright 2013 Pedro Navarro Perez
|
|
||||||
#All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# @author: Pedro Navarro Perez
|
|
||||||
# @author: Alessandro Pilotti, Cloudbase Solutions Srl
|
|
||||||
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
eventlet.monkey_patch()
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.agent.common import config
|
|
||||||
from neutron.agent import rpc as agent_rpc
|
|
||||||
from neutron.agent import securitygroups_rpc as sg_rpc
|
|
||||||
from neutron.common import config as common_config
|
|
||||||
from neutron.common import constants as n_const
|
|
||||||
from neutron.common import rpc_compat
|
|
||||||
from neutron.common import topics
|
|
||||||
from neutron import context
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.openstack.common import loopingcall
|
|
||||||
from neutron.plugins.common import constants as p_const
|
|
||||||
from neutron.plugins.hyperv.agent import utils
|
|
||||||
from neutron.plugins.hyperv.agent import utilsfactory
|
|
||||||
from neutron.plugins.hyperv.common import constants
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
agent_opts = [
|
|
||||||
cfg.ListOpt(
|
|
||||||
'physical_network_vswitch_mappings',
|
|
||||||
default=[],
|
|
||||||
help=_('List of <physical_network>:<vswitch> '
|
|
||||||
'where the physical networks can be expressed with '
|
|
||||||
'wildcards, e.g.: ."*:external"')),
|
|
||||||
cfg.StrOpt(
|
|
||||||
'local_network_vswitch',
|
|
||||||
default='private',
|
|
||||||
help=_('Private vswitch name used for local networks')),
|
|
||||||
cfg.IntOpt('polling_interval', default=2,
|
|
||||||
help=_("The number of seconds the agent will wait between "
|
|
||||||
"polling for local device changes.")),
|
|
||||||
cfg.BoolOpt('enable_metrics_collection',
|
|
||||||
default=False,
|
|
||||||
help=_('Enables metrics collections for switch ports by using '
|
|
||||||
'Hyper-V\'s metric APIs. Collected data can by '
|
|
||||||
'retrieved by other apps and services, e.g.: '
|
|
||||||
'Ceilometer. Requires Hyper-V / Windows Server 2012 '
|
|
||||||
'and above')),
|
|
||||||
cfg.IntOpt('metrics_max_retries',
|
|
||||||
default=100,
|
|
||||||
help=_('Specifies the maximum number of retries to enable '
|
|
||||||
'Hyper-V\'s port metrics collection. The agent will try '
|
|
||||||
'to enable the feature once every polling_interval '
|
|
||||||
'period for at most metrics_max_retries or until it '
|
|
||||||
'succeedes.'))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(agent_opts, "AGENT")
|
|
||||||
config.register_agent_state_opts_helper(cfg.CONF)
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVSecurityAgent(rpc_compat.RpcCallback,
|
|
||||||
sg_rpc.SecurityGroupAgentRpcMixin):
|
|
||||||
# Set RPC API version to 1.1 by default.
|
|
||||||
RPC_API_VERSION = '1.1'
|
|
||||||
|
|
||||||
def __init__(self, context, plugin_rpc):
|
|
||||||
super(HyperVSecurityAgent, self).__init__()
|
|
||||||
self.context = context
|
|
||||||
self.plugin_rpc = plugin_rpc
|
|
||||||
|
|
||||||
if sg_rpc.is_firewall_enabled():
|
|
||||||
self.init_firewall()
|
|
||||||
self._setup_rpc()
|
|
||||||
|
|
||||||
def _setup_rpc(self):
|
|
||||||
self.topic = topics.AGENT
|
|
||||||
self.endpoints = [HyperVSecurityCallbackMixin(self)]
|
|
||||||
consumers = [[topics.SECURITY_GROUP, topics.UPDATE]]
|
|
||||||
|
|
||||||
self.connection = agent_rpc.create_consumers(self.endpoints,
|
|
||||||
self.topic,
|
|
||||||
consumers)
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVSecurityCallbackMixin(rpc_compat.RpcCallback,
|
|
||||||
sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
|
||||||
# Set RPC API version to 1.1 by default.
|
|
||||||
RPC_API_VERSION = '1.1'
|
|
||||||
|
|
||||||
def __init__(self, sg_agent):
|
|
||||||
super(HyperVSecurityCallbackMixin, self).__init__()
|
|
||||||
self.sg_agent = sg_agent
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVPluginApi(agent_rpc.PluginApi,
|
|
||||||
sg_rpc.SecurityGroupServerRpcApiMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVNeutronAgent(rpc_compat.RpcCallback):
|
|
||||||
# Set RPC API version to 1.0 by default.
|
|
||||||
RPC_API_VERSION = '1.0'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(HyperVNeutronAgent, self).__init__()
|
|
||||||
self._utils = utilsfactory.get_hypervutils()
|
|
||||||
self._polling_interval = CONF.AGENT.polling_interval
|
|
||||||
self._load_physical_network_mappings()
|
|
||||||
self._network_vswitch_map = {}
|
|
||||||
self._port_metric_retries = {}
|
|
||||||
self._set_agent_state()
|
|
||||||
self._setup_rpc()
|
|
||||||
|
|
||||||
def _set_agent_state(self):
|
|
||||||
self.agent_state = {
|
|
||||||
'binary': 'neutron-hyperv-agent',
|
|
||||||
'host': cfg.CONF.host,
|
|
||||||
'topic': n_const.L2_AGENT_TOPIC,
|
|
||||||
'configurations': {'vswitch_mappings':
|
|
||||||
self._physical_network_mappings},
|
|
||||||
'agent_type': n_const.AGENT_TYPE_HYPERV,
|
|
||||||
'start_flag': True}
|
|
||||||
|
|
||||||
def _report_state(self):
|
|
||||||
try:
|
|
||||||
self.state_rpc.report_state(self.context,
|
|
||||||
self.agent_state)
|
|
||||||
self.agent_state.pop('start_flag', None)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.exception(_("Failed reporting state! %s"), ex)
|
|
||||||
|
|
||||||
def _setup_rpc(self):
|
|
||||||
self.agent_id = 'hyperv_%s' % platform.node()
|
|
||||||
self.topic = topics.AGENT
|
|
||||||
self.plugin_rpc = HyperVPluginApi(topics.PLUGIN)
|
|
||||||
|
|
||||||
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
|
|
||||||
|
|
||||||
# RPC network init
|
|
||||||
self.context = context.get_admin_context_without_session()
|
|
||||||
# Handle updates from service
|
|
||||||
self.endpoints = [self]
|
|
||||||
# Define the listening consumers for the agent
|
|
||||||
consumers = [[topics.PORT, topics.UPDATE],
|
|
||||||
[topics.NETWORK, topics.DELETE],
|
|
||||||
[topics.PORT, topics.DELETE],
|
|
||||||
[constants.TUNNEL, topics.UPDATE]]
|
|
||||||
self.connection = agent_rpc.create_consumers(self.endpoints,
|
|
||||||
self.topic,
|
|
||||||
consumers)
|
|
||||||
|
|
||||||
self.sec_groups_agent = HyperVSecurityAgent(
|
|
||||||
self.context, self.plugin_rpc)
|
|
||||||
report_interval = CONF.AGENT.report_interval
|
|
||||||
if report_interval:
|
|
||||||
heartbeat = loopingcall.FixedIntervalLoopingCall(
|
|
||||||
self._report_state)
|
|
||||||
heartbeat.start(interval=report_interval)
|
|
||||||
|
|
||||||
def _load_physical_network_mappings(self):
|
|
||||||
self._physical_network_mappings = {}
|
|
||||||
for mapping in CONF.AGENT.physical_network_vswitch_mappings:
|
|
||||||
parts = mapping.split(':')
|
|
||||||
if len(parts) != 2:
|
|
||||||
LOG.debug(_('Invalid physical network mapping: %s'), mapping)
|
|
||||||
else:
|
|
||||||
pattern = re.escape(parts[0].strip()).replace('\\*', '.*')
|
|
||||||
vswitch = parts[1].strip()
|
|
||||||
self._physical_network_mappings[pattern] = vswitch
|
|
||||||
|
|
||||||
def _get_vswitch_for_physical_network(self, phys_network_name):
|
|
||||||
for pattern in self._physical_network_mappings:
|
|
||||||
if phys_network_name is None:
|
|
||||||
phys_network_name = ''
|
|
||||||
if re.match(pattern, phys_network_name):
|
|
||||||
return self._physical_network_mappings[pattern]
|
|
||||||
# Not found in the mappings, the vswitch has the same name
|
|
||||||
return phys_network_name
|
|
||||||
|
|
||||||
def _get_network_vswitch_map_by_port_id(self, port_id):
|
|
||||||
for network_id, map in self._network_vswitch_map.iteritems():
|
|
||||||
if port_id in map['ports']:
|
|
||||||
return (network_id, map)
|
|
||||||
|
|
||||||
def network_delete(self, context, network_id=None):
|
|
||||||
LOG.debug(_("network_delete received. "
|
|
||||||
"Deleting network %s"), network_id)
|
|
||||||
# The network may not be defined on this agent
|
|
||||||
if network_id in self._network_vswitch_map:
|
|
||||||
self._reclaim_local_network(network_id)
|
|
||||||
else:
|
|
||||||
LOG.debug(_("Network %s not defined on agent."), network_id)
|
|
||||||
|
|
||||||
def port_delete(self, context, port_id=None):
|
|
||||||
LOG.debug(_("port_delete received"))
|
|
||||||
self._port_unbound(port_id)
|
|
||||||
|
|
||||||
def port_update(self, context, port=None, network_type=None,
|
|
||||||
segmentation_id=None, physical_network=None):
|
|
||||||
LOG.debug(_("port_update received"))
|
|
||||||
if CONF.SECURITYGROUP.enable_security_group:
|
|
||||||
if 'security_groups' in port:
|
|
||||||
self.sec_groups_agent.refresh_firewall()
|
|
||||||
|
|
||||||
self._treat_vif_port(
|
|
||||||
port['id'], port['network_id'],
|
|
||||||
network_type, physical_network,
|
|
||||||
segmentation_id, port['admin_state_up'])
|
|
||||||
|
|
||||||
def _get_vswitch_name(self, network_type, physical_network):
|
|
||||||
if network_type != p_const.TYPE_LOCAL:
|
|
||||||
vswitch_name = self._get_vswitch_for_physical_network(
|
|
||||||
physical_network)
|
|
||||||
else:
|
|
||||||
vswitch_name = CONF.AGENT.local_network_vswitch
|
|
||||||
return vswitch_name
|
|
||||||
|
|
||||||
def _provision_network(self, port_id,
|
|
||||||
net_uuid, network_type,
|
|
||||||
physical_network,
|
|
||||||
segmentation_id):
|
|
||||||
LOG.info(_("Provisioning network %s"), net_uuid)
|
|
||||||
|
|
||||||
vswitch_name = self._get_vswitch_name(network_type, physical_network)
|
|
||||||
|
|
||||||
if network_type in [p_const.TYPE_VLAN, p_const.TYPE_FLAT]:
|
|
||||||
#Nothing to do
|
|
||||||
pass
|
|
||||||
elif network_type == p_const.TYPE_LOCAL:
|
|
||||||
#TODO(alexpilotti): Check that the switch type is private
|
|
||||||
#or create it if not existing
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise utils.HyperVException(
|
|
||||||
msg=(_("Cannot provision unknown network type %(network_type)s"
|
|
||||||
" for network %(net_uuid)s") %
|
|
||||||
dict(network_type=network_type, net_uuid=net_uuid)))
|
|
||||||
|
|
||||||
map = {
|
|
||||||
'network_type': network_type,
|
|
||||||
'vswitch_name': vswitch_name,
|
|
||||||
'ports': [],
|
|
||||||
'vlan_id': segmentation_id}
|
|
||||||
self._network_vswitch_map[net_uuid] = map
|
|
||||||
|
|
||||||
def _reclaim_local_network(self, net_uuid):
|
|
||||||
LOG.info(_("Reclaiming local network %s"), net_uuid)
|
|
||||||
del self._network_vswitch_map[net_uuid]
|
|
||||||
|
|
||||||
def _port_bound(self, port_id,
|
|
||||||
net_uuid,
|
|
||||||
network_type,
|
|
||||||
physical_network,
|
|
||||||
segmentation_id):
|
|
||||||
LOG.debug(_("Binding port %s"), port_id)
|
|
||||||
|
|
||||||
if net_uuid not in self._network_vswitch_map:
|
|
||||||
self._provision_network(
|
|
||||||
port_id, net_uuid, network_type,
|
|
||||||
physical_network, segmentation_id)
|
|
||||||
|
|
||||||
map = self._network_vswitch_map[net_uuid]
|
|
||||||
map['ports'].append(port_id)
|
|
||||||
|
|
||||||
self._utils.connect_vnic_to_vswitch(map['vswitch_name'], port_id)
|
|
||||||
|
|
||||||
if network_type == p_const.TYPE_VLAN:
|
|
||||||
LOG.info(_('Binding VLAN ID %(segmentation_id)s '
|
|
||||||
'to switch port %(port_id)s'),
|
|
||||||
dict(segmentation_id=segmentation_id, port_id=port_id))
|
|
||||||
self._utils.set_vswitch_port_vlan_id(
|
|
||||||
segmentation_id,
|
|
||||||
port_id)
|
|
||||||
elif network_type == p_const.TYPE_FLAT:
|
|
||||||
#Nothing to do
|
|
||||||
pass
|
|
||||||
elif network_type == p_const.TYPE_LOCAL:
|
|
||||||
#Nothing to do
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
LOG.error(_('Unsupported network type %s'), network_type)
|
|
||||||
|
|
||||||
if CONF.AGENT.enable_metrics_collection:
|
|
||||||
self._utils.enable_port_metrics_collection(port_id)
|
|
||||||
self._port_metric_retries[port_id] = CONF.AGENT.metrics_max_retries
|
|
||||||
|
|
||||||
def _port_unbound(self, port_id):
|
|
||||||
(net_uuid, map) = self._get_network_vswitch_map_by_port_id(port_id)
|
|
||||||
if net_uuid not in self._network_vswitch_map:
|
|
||||||
LOG.info(_('Network %s is not avalailable on this agent'),
|
|
||||||
net_uuid)
|
|
||||||
return
|
|
||||||
|
|
||||||
LOG.debug(_("Unbinding port %s"), port_id)
|
|
||||||
self._utils.disconnect_switch_port(map['vswitch_name'], port_id, True)
|
|
||||||
|
|
||||||
if not map['ports']:
|
|
||||||
self._reclaim_local_network(net_uuid)
|
|
||||||
|
|
||||||
def _port_enable_control_metrics(self):
|
|
||||||
if not CONF.AGENT.enable_metrics_collection:
|
|
||||||
return
|
|
||||||
|
|
||||||
for port_id in self._port_metric_retries.keys():
|
|
||||||
if self._utils.can_enable_control_metrics(port_id):
|
|
||||||
self._utils.enable_control_metrics(port_id)
|
|
||||||
LOG.info(_('Port metrics enabled for port: %s'), port_id)
|
|
||||||
del self._port_metric_retries[port_id]
|
|
||||||
elif self._port_metric_retries[port_id] < 1:
|
|
||||||
self._utils.enable_control_metrics(port_id)
|
|
||||||
LOG.error(_('Port metrics raw enabling for port: %s'), port_id)
|
|
||||||
del self._port_metric_retries[port_id]
|
|
||||||
else:
|
|
||||||
self._port_metric_retries[port_id] -= 1
|
|
||||||
|
|
||||||
def _update_ports(self, registered_ports):
|
|
||||||
ports = self._utils.get_vnic_ids()
|
|
||||||
if ports == registered_ports:
|
|
||||||
return
|
|
||||||
added = ports - registered_ports
|
|
||||||
removed = registered_ports - ports
|
|
||||||
return {'current': ports,
|
|
||||||
'added': added,
|
|
||||||
'removed': removed}
|
|
||||||
|
|
||||||
def _treat_vif_port(self, port_id, network_id, network_type,
|
|
||||||
physical_network, segmentation_id,
|
|
||||||
admin_state_up):
|
|
||||||
if self._utils.vnic_port_exists(port_id):
|
|
||||||
if admin_state_up:
|
|
||||||
self._port_bound(port_id, network_id, network_type,
|
|
||||||
physical_network, segmentation_id)
|
|
||||||
else:
|
|
||||||
self._port_unbound(port_id)
|
|
||||||
else:
|
|
||||||
LOG.debug(_("No port %s defined on agent."), port_id)
|
|
||||||
|
|
||||||
def _treat_devices_added(self, devices):
|
|
||||||
resync = False
|
|
||||||
for device in devices:
|
|
||||||
LOG.info(_("Adding port %s"), device)
|
|
||||||
try:
|
|
||||||
device_details = self.plugin_rpc.get_device_details(
|
|
||||||
self.context,
|
|
||||||
device,
|
|
||||||
self.agent_id)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.debug(
|
|
||||||
_("Unable to get port details for "
|
|
||||||
"device %(device)s: %(e)s"),
|
|
||||||
{'device': device, 'e': e})
|
|
||||||
resync = True
|
|
||||||
continue
|
|
||||||
if 'port_id' in device_details:
|
|
||||||
LOG.info(
|
|
||||||
_("Port %(device)s updated. Details: %(device_details)s"),
|
|
||||||
{'device': device, 'device_details': device_details})
|
|
||||||
self._treat_vif_port(
|
|
||||||
device_details['port_id'],
|
|
||||||
device_details['network_id'],
|
|
||||||
device_details['network_type'],
|
|
||||||
device_details['physical_network'],
|
|
||||||
device_details['segmentation_id'],
|
|
||||||
device_details['admin_state_up'])
|
|
||||||
|
|
||||||
# check if security groups is enabled.
|
|
||||||
# if not, teardown the security group rules
|
|
||||||
if CONF.SECURITYGROUP.enable_security_group:
|
|
||||||
self.sec_groups_agent.prepare_devices_filter([device])
|
|
||||||
else:
|
|
||||||
self._utils.remove_all_security_rules(
|
|
||||||
device_details['port_id'])
|
|
||||||
self.plugin_rpc.update_device_up(self.context,
|
|
||||||
device,
|
|
||||||
self.agent_id,
|
|
||||||
cfg.CONF.host)
|
|
||||||
return resync
|
|
||||||
|
|
||||||
def _treat_devices_removed(self, devices):
|
|
||||||
resync = False
|
|
||||||
for device in devices:
|
|
||||||
LOG.info(_("Removing port %s"), device)
|
|
||||||
try:
|
|
||||||
self.plugin_rpc.update_device_down(self.context,
|
|
||||||
device,
|
|
||||||
self.agent_id,
|
|
||||||
cfg.CONF.host)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.debug(
|
|
||||||
_("Removing port failed for device %(device)s: %(e)s"),
|
|
||||||
dict(device=device, e=e))
|
|
||||||
resync = True
|
|
||||||
continue
|
|
||||||
self._port_unbound(device)
|
|
||||||
return resync
|
|
||||||
|
|
||||||
def _process_network_ports(self, port_info):
|
|
||||||
resync_a = False
|
|
||||||
resync_b = False
|
|
||||||
if 'added' in port_info:
|
|
||||||
resync_a = self._treat_devices_added(port_info['added'])
|
|
||||||
if 'removed' in port_info:
|
|
||||||
resync_b = self._treat_devices_removed(port_info['removed'])
|
|
||||||
# If one of the above operations fails => resync with plugin
|
|
||||||
return (resync_a | resync_b)
|
|
||||||
|
|
||||||
def daemon_loop(self):
|
|
||||||
sync = True
|
|
||||||
ports = set()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
start = time.time()
|
|
||||||
if sync:
|
|
||||||
LOG.info(_("Agent out of sync with plugin!"))
|
|
||||||
ports.clear()
|
|
||||||
sync = False
|
|
||||||
|
|
||||||
port_info = self._update_ports(ports)
|
|
||||||
|
|
||||||
# notify plugin about port deltas
|
|
||||||
if port_info:
|
|
||||||
LOG.debug(_("Agent loop has new devices!"))
|
|
||||||
# If treat devices fails - must resync with plugin
|
|
||||||
sync = self._process_network_ports(port_info)
|
|
||||||
ports = port_info['current']
|
|
||||||
|
|
||||||
self._port_enable_control_metrics()
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(_("Error in agent event loop: %s"), e)
|
|
||||||
sync = True
|
|
||||||
|
|
||||||
# sleep till end of polling interval
|
|
||||||
elapsed = (time.time() - start)
|
|
||||||
if (elapsed < self._polling_interval):
|
|
||||||
time.sleep(self._polling_interval - elapsed)
|
|
||||||
else:
|
|
||||||
LOG.debug(_("Loop iteration exceeded interval "
|
|
||||||
"(%(polling_interval)s vs. %(elapsed)s)"),
|
|
||||||
{'polling_interval': self._polling_interval,
|
|
||||||
'elapsed': elapsed})
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
common_config.init(sys.argv[1:])
|
|
||||||
common_config.setup_logging(cfg.CONF)
|
|
||||||
|
|
||||||
plugin = HyperVNeutronAgent()
|
|
||||||
|
|
||||||
# Start everything.
|
|
||||||
LOG.info(_("Agent initialized successfully, now running... "))
|
|
||||||
plugin.daemon_loop()
|
|
|
@ -1,146 +0,0 @@
|
||||||
#Copyright 2014 Cloudbase Solutions SRL
|
|
||||||
#All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# @author: Claudiu Belu, Cloudbase Solutions Srl
|
|
||||||
|
|
||||||
from neutron.agent import firewall
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.hyperv.agent import utilsfactory
|
|
||||||
from neutron.plugins.hyperv.agent import utilsv2
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVSecurityGroupsDriver(firewall.FirewallDriver):
|
|
||||||
"""Security Groups Driver.
|
|
||||||
|
|
||||||
Security Groups implementation for Hyper-V VMs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_ACL_PROP_MAP = {
|
|
||||||
'direction': {'ingress': utilsv2.HyperVUtilsV2._ACL_DIR_IN,
|
|
||||||
'egress': utilsv2.HyperVUtilsV2._ACL_DIR_OUT},
|
|
||||||
'ethertype': {'IPv4': utilsv2.HyperVUtilsV2._ACL_TYPE_IPV4,
|
|
||||||
'IPv6': utilsv2.HyperVUtilsV2._ACL_TYPE_IPV6},
|
|
||||||
'protocol': {'icmp': utilsv2.HyperVUtilsV2._ICMP_PROTOCOL},
|
|
||||||
'default': "ANY",
|
|
||||||
'address_default': {'IPv4': '0.0.0.0/0', 'IPv6': '::/0'}
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._utils = utilsfactory.get_hypervutils()
|
|
||||||
self._security_ports = {}
|
|
||||||
|
|
||||||
def prepare_port_filter(self, port):
|
|
||||||
LOG.debug('Creating port %s rules' % len(port['security_group_rules']))
|
|
||||||
|
|
||||||
# newly created port, add default rules.
|
|
||||||
if port['device'] not in self._security_ports:
|
|
||||||
LOG.debug('Creating default reject rules.')
|
|
||||||
self._utils.create_default_reject_all_rules(port['id'])
|
|
||||||
|
|
||||||
self._security_ports[port['device']] = port
|
|
||||||
self._create_port_rules(port['id'], port['security_group_rules'])
|
|
||||||
|
|
||||||
def _create_port_rules(self, port_id, rules):
|
|
||||||
for rule in rules:
|
|
||||||
param_map = self._create_param_map(rule)
|
|
||||||
try:
|
|
||||||
self._utils.create_security_rule(port_id, **param_map)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(_('Hyper-V Exception: %(hyperv_exeption)s while '
|
|
||||||
'adding rule: %(rule)s'),
|
|
||||||
dict(hyperv_exeption=ex, rule=rule))
|
|
||||||
|
|
||||||
def _remove_port_rules(self, port_id, rules):
|
|
||||||
for rule in rules:
|
|
||||||
param_map = self._create_param_map(rule)
|
|
||||||
try:
|
|
||||||
self._utils.remove_security_rule(port_id, **param_map)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error(_('Hyper-V Exception: %(hyperv_exeption)s while '
|
|
||||||
'removing rule: %(rule)s'),
|
|
||||||
dict(hyperv_exeption=ex, rule=rule))
|
|
||||||
|
|
||||||
def _create_param_map(self, rule):
|
|
||||||
if 'port_range_min' in rule and 'port_range_max' in rule:
|
|
||||||
local_port = '%s-%s' % (rule['port_range_min'],
|
|
||||||
rule['port_range_max'])
|
|
||||||
else:
|
|
||||||
local_port = self._ACL_PROP_MAP['default']
|
|
||||||
|
|
||||||
return {
|
|
||||||
'direction': self._ACL_PROP_MAP['direction'][rule['direction']],
|
|
||||||
'acl_type': self._ACL_PROP_MAP['ethertype'][rule['ethertype']],
|
|
||||||
'local_port': local_port,
|
|
||||||
'protocol': self._get_rule_protocol(rule),
|
|
||||||
'remote_address': self._get_rule_remote_address(rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
def apply_port_filter(self, port):
|
|
||||||
LOG.info(_('Aplying port filter.'))
|
|
||||||
|
|
||||||
def update_port_filter(self, port):
|
|
||||||
LOG.info(_('Updating port rules.'))
|
|
||||||
|
|
||||||
if port['device'] not in self._security_ports:
|
|
||||||
self.prepare_port_filter(port)
|
|
||||||
return
|
|
||||||
|
|
||||||
old_port = self._security_ports[port['device']]
|
|
||||||
rules = old_port['security_group_rules']
|
|
||||||
param_port_rules = port['security_group_rules']
|
|
||||||
|
|
||||||
new_rules = [r for r in param_port_rules if r not in rules]
|
|
||||||
remove_rules = [r for r in rules if r not in param_port_rules]
|
|
||||||
|
|
||||||
LOG.info(_("Creating %(new)s new rules, removing %(old)s "
|
|
||||||
"old rules."),
|
|
||||||
{'new': len(new_rules),
|
|
||||||
'old': len(remove_rules)})
|
|
||||||
|
|
||||||
self._remove_port_rules(old_port['id'], remove_rules)
|
|
||||||
self._create_port_rules(port['id'], new_rules)
|
|
||||||
|
|
||||||
self._security_ports[port['device']] = port
|
|
||||||
|
|
||||||
def remove_port_filter(self, port):
|
|
||||||
LOG.info(_('Removing port filter'))
|
|
||||||
self._security_ports.pop(port['device'], None)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ports(self):
|
|
||||||
return self._security_ports
|
|
||||||
|
|
||||||
def _get_rule_remote_address(self, rule):
|
|
||||||
if rule['direction'] is 'ingress':
|
|
||||||
ip_prefix = 'source_ip_prefix'
|
|
||||||
else:
|
|
||||||
ip_prefix = 'dest_ip_prefix'
|
|
||||||
|
|
||||||
if ip_prefix in rule:
|
|
||||||
return rule[ip_prefix]
|
|
||||||
return self._ACL_PROP_MAP['address_default'][rule['ethertype']]
|
|
||||||
|
|
||||||
def _get_rule_protocol(self, rule):
|
|
||||||
protocol = self._get_rule_prop_or_default(rule, 'protocol')
|
|
||||||
if protocol in self._ACL_PROP_MAP['protocol'].keys():
|
|
||||||
return self._ACL_PROP_MAP['protocol'][protocol]
|
|
||||||
|
|
||||||
return protocol
|
|
||||||
|
|
||||||
def _get_rule_prop_or_default(self, rule, prop):
|
|
||||||
if prop in rule:
|
|
||||||
return rule[prop]
|
|
||||||
return self._ACL_PROP_MAP['default']
|
|
|
@ -1,256 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
# Copyright 2013 Pedro Navarro Perez
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# @author: Pedro Navarro Perez
|
|
||||||
# @author: Alessandro Pilotti, Cloudbase Solutions Srl
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.common import exceptions as n_exc
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
|
|
||||||
# Check needed for unit testing on Unix
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
import wmi
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVException(n_exc.NeutronException):
|
|
||||||
message = _('HyperVException: %(msg)s')
|
|
||||||
|
|
||||||
WMI_JOB_STATE_STARTED = 4096
|
|
||||||
WMI_JOB_STATE_RUNNING = 4
|
|
||||||
WMI_JOB_STATE_COMPLETED = 7
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVUtils(object):
|
|
||||||
|
|
||||||
_ETHERNET_SWITCH_PORT = 'Msvm_SwitchPort'
|
|
||||||
|
|
||||||
_wmi_namespace = '//./root/virtualization'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._wmi_conn = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _conn(self):
|
|
||||||
if self._wmi_conn is None:
|
|
||||||
self._wmi_conn = wmi.WMI(moniker=self._wmi_namespace)
|
|
||||||
return self._wmi_conn
|
|
||||||
|
|
||||||
def get_switch_ports(self, vswitch_name):
|
|
||||||
vswitch = self._get_vswitch(vswitch_name)
|
|
||||||
vswitch_ports = vswitch.associators(
|
|
||||||
wmi_result_class=self._ETHERNET_SWITCH_PORT)
|
|
||||||
return set(p.Name for p in vswitch_ports)
|
|
||||||
|
|
||||||
def vnic_port_exists(self, port_id):
|
|
||||||
try:
|
|
||||||
self._get_vnic_settings(port_id)
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_vnic_ids(self):
|
|
||||||
return set(
|
|
||||||
p.ElementName
|
|
||||||
for p in self._conn.Msvm_SyntheticEthernetPortSettingData()
|
|
||||||
if p.ElementName is not None)
|
|
||||||
|
|
||||||
def _get_vnic_settings(self, vnic_name):
|
|
||||||
vnic_settings = self._conn.Msvm_SyntheticEthernetPortSettingData(
|
|
||||||
ElementName=vnic_name)
|
|
||||||
if not vnic_settings:
|
|
||||||
raise HyperVException(msg=_('Vnic not found: %s') % vnic_name)
|
|
||||||
return vnic_settings[0]
|
|
||||||
|
|
||||||
def connect_vnic_to_vswitch(self, vswitch_name, switch_port_name):
|
|
||||||
vnic_settings = self._get_vnic_settings(switch_port_name)
|
|
||||||
if not vnic_settings.Connection or not vnic_settings.Connection[0]:
|
|
||||||
port = self.get_port_by_id(switch_port_name, vswitch_name)
|
|
||||||
if port:
|
|
||||||
port_path = port.Path_()
|
|
||||||
else:
|
|
||||||
port_path = self._create_switch_port(
|
|
||||||
vswitch_name, switch_port_name)
|
|
||||||
vnic_settings.Connection = [port_path]
|
|
||||||
self._modify_virt_resource(vnic_settings)
|
|
||||||
|
|
||||||
def _get_vm_from_res_setting_data(self, res_setting_data):
|
|
||||||
sd = res_setting_data.associators(
|
|
||||||
wmi_result_class='Msvm_VirtualSystemSettingData')
|
|
||||||
vm = sd[0].associators(
|
|
||||||
wmi_result_class='Msvm_ComputerSystem')
|
|
||||||
return vm[0]
|
|
||||||
|
|
||||||
def _modify_virt_resource(self, res_setting_data):
|
|
||||||
vm = self._get_vm_from_res_setting_data(res_setting_data)
|
|
||||||
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
(job_path, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
|
|
||||||
vm.Path_(), [res_setting_data.GetText_(1)])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
def _check_job_status(self, ret_val, jobpath):
|
|
||||||
"""Poll WMI job state for completion."""
|
|
||||||
if not ret_val:
|
|
||||||
return
|
|
||||||
elif ret_val not in [WMI_JOB_STATE_STARTED, WMI_JOB_STATE_RUNNING]:
|
|
||||||
raise HyperVException(msg=_('Job failed with error %d') % ret_val)
|
|
||||||
|
|
||||||
job_wmi_path = jobpath.replace('\\', '/')
|
|
||||||
job = wmi.WMI(moniker=job_wmi_path)
|
|
||||||
|
|
||||||
while job.JobState == WMI_JOB_STATE_RUNNING:
|
|
||||||
time.sleep(0.1)
|
|
||||||
job = wmi.WMI(moniker=job_wmi_path)
|
|
||||||
if job.JobState != WMI_JOB_STATE_COMPLETED:
|
|
||||||
job_state = job.JobState
|
|
||||||
if job.path().Class == "Msvm_ConcreteJob":
|
|
||||||
err_sum_desc = job.ErrorSummaryDescription
|
|
||||||
err_desc = job.ErrorDescription
|
|
||||||
err_code = job.ErrorCode
|
|
||||||
data = {'job_state': job_state,
|
|
||||||
'err_sum_desc': err_sum_desc,
|
|
||||||
'err_desc': err_desc,
|
|
||||||
'err_code': err_code}
|
|
||||||
raise HyperVException(
|
|
||||||
msg=_("WMI job failed with status %(job_state)d. "
|
|
||||||
"Error details: %(err_sum_desc)s - %(err_desc)s - "
|
|
||||||
"Error code: %(err_code)d") % data)
|
|
||||||
else:
|
|
||||||
(error, ret_val) = job.GetError()
|
|
||||||
if not ret_val and error:
|
|
||||||
data = {'job_state': job_state,
|
|
||||||
'error': error}
|
|
||||||
raise HyperVException(
|
|
||||||
msg=_("WMI job failed with status %(job_state)d. "
|
|
||||||
"Error details: %(error)s") % data)
|
|
||||||
else:
|
|
||||||
raise HyperVException(
|
|
||||||
msg=_("WMI job failed with status %d. "
|
|
||||||
"No error description available") % job_state)
|
|
||||||
|
|
||||||
desc = job.Description
|
|
||||||
elap = job.ElapsedTime
|
|
||||||
LOG.debug(_("WMI job succeeded: %(desc)s, Elapsed=%(elap)s"),
|
|
||||||
{'desc': desc, 'elap': elap})
|
|
||||||
|
|
||||||
def _create_switch_port(self, vswitch_name, switch_port_name):
|
|
||||||
"""Creates a switch port."""
|
|
||||||
switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0]
|
|
||||||
vswitch_path = self._get_vswitch(vswitch_name).path_()
|
|
||||||
(new_port, ret_val) = switch_svc.CreateSwitchPort(
|
|
||||||
Name=switch_port_name,
|
|
||||||
FriendlyName=switch_port_name,
|
|
||||||
ScopeOfResidence="",
|
|
||||||
VirtualSwitch=vswitch_path)
|
|
||||||
if ret_val != 0:
|
|
||||||
raise HyperVException(
|
|
||||||
msg=_('Failed creating port for %s') % vswitch_name)
|
|
||||||
return new_port
|
|
||||||
|
|
||||||
def disconnect_switch_port(
|
|
||||||
self, vswitch_name, switch_port_name, delete_port):
|
|
||||||
"""Disconnects the switch port."""
|
|
||||||
switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0]
|
|
||||||
switch_port_path = self._get_switch_port_path_by_name(
|
|
||||||
switch_port_name)
|
|
||||||
if not switch_port_path:
|
|
||||||
# Port not found. It happens when the VM was already deleted.
|
|
||||||
return
|
|
||||||
|
|
||||||
(ret_val, ) = switch_svc.DisconnectSwitchPort(
|
|
||||||
SwitchPort=switch_port_path)
|
|
||||||
if ret_val != 0:
|
|
||||||
data = {'switch_port_name': switch_port_name,
|
|
||||||
'vswitch_name': vswitch_name,
|
|
||||||
'ret_val': ret_val}
|
|
||||||
raise HyperVException(
|
|
||||||
msg=_('Failed to disconnect port %(switch_port_name)s '
|
|
||||||
'from switch %(vswitch_name)s '
|
|
||||||
'with error %(ret_val)s') % data)
|
|
||||||
if delete_port:
|
|
||||||
(ret_val, ) = switch_svc.DeleteSwitchPort(
|
|
||||||
SwitchPort=switch_port_path)
|
|
||||||
if ret_val != 0:
|
|
||||||
data = {'switch_port_name': switch_port_name,
|
|
||||||
'vswitch_name': vswitch_name,
|
|
||||||
'ret_val': ret_val}
|
|
||||||
raise HyperVException(
|
|
||||||
msg=_('Failed to delete port %(switch_port_name)s '
|
|
||||||
'from switch %(vswitch_name)s '
|
|
||||||
'with error %(ret_val)s') % data)
|
|
||||||
|
|
||||||
def _get_vswitch(self, vswitch_name):
|
|
||||||
vswitch = self._conn.Msvm_VirtualSwitch(ElementName=vswitch_name)
|
|
||||||
if not vswitch:
|
|
||||||
raise HyperVException(msg=_('VSwitch not found: %s') %
|
|
||||||
vswitch_name)
|
|
||||||
return vswitch[0]
|
|
||||||
|
|
||||||
def _get_vswitch_external_port(self, vswitch):
|
|
||||||
vswitch_ports = vswitch.associators(
|
|
||||||
wmi_result_class=self._ETHERNET_SWITCH_PORT)
|
|
||||||
for vswitch_port in vswitch_ports:
|
|
||||||
lan_endpoints = vswitch_port.associators(
|
|
||||||
wmi_result_class='Msvm_SwitchLanEndpoint')
|
|
||||||
if lan_endpoints:
|
|
||||||
ext_port = lan_endpoints[0].associators(
|
|
||||||
wmi_result_class='Msvm_ExternalEthernetPort')
|
|
||||||
if ext_port:
|
|
||||||
return vswitch_port
|
|
||||||
|
|
||||||
def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name):
|
|
||||||
vlan_endpoint_settings = self._conn.Msvm_VLANEndpointSettingData(
|
|
||||||
ElementName=switch_port_name)[0]
|
|
||||||
if vlan_endpoint_settings.AccessVLAN != vlan_id:
|
|
||||||
vlan_endpoint_settings.AccessVLAN = vlan_id
|
|
||||||
vlan_endpoint_settings.put()
|
|
||||||
|
|
||||||
def _get_switch_port_path_by_name(self, switch_port_name):
|
|
||||||
vswitch = self._conn.Msvm_SwitchPort(ElementName=switch_port_name)
|
|
||||||
if vswitch:
|
|
||||||
return vswitch[0].path_()
|
|
||||||
|
|
||||||
def get_vswitch_id(self, vswitch_name):
|
|
||||||
vswitch = self._get_vswitch(vswitch_name)
|
|
||||||
return vswitch.Name
|
|
||||||
|
|
||||||
def get_port_by_id(self, port_id, vswitch_name):
|
|
||||||
vswitch = self._get_vswitch(vswitch_name)
|
|
||||||
switch_ports = vswitch.associators(
|
|
||||||
wmi_result_class=self._ETHERNET_SWITCH_PORT)
|
|
||||||
for switch_port in switch_ports:
|
|
||||||
if (switch_port.ElementName == port_id):
|
|
||||||
return switch_port
|
|
||||||
|
|
||||||
def enable_port_metrics_collection(self, switch_port_name):
|
|
||||||
raise NotImplementedError(_("Metrics collection is not supported on "
|
|
||||||
"this version of Hyper-V"))
|
|
||||||
|
|
||||||
def enable_control_metrics(self, switch_port_name):
|
|
||||||
raise NotImplementedError(_("Metrics collection is not supported on "
|
|
||||||
"this version of Hyper-V"))
|
|
||||||
|
|
||||||
def can_enable_control_metrics(self, switch_port_name):
|
|
||||||
return False
|
|
|
@ -1,72 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# @author: Claudiu Belu, Cloudbase Solutions Srl
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.hyperv.agent import utils
|
|
||||||
from neutron.plugins.hyperv.agent import utilsv2
|
|
||||||
|
|
||||||
# Check needed for unit testing on Unix
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
import wmi
|
|
||||||
|
|
||||||
hyper_opts = [
|
|
||||||
cfg.BoolOpt('force_hyperv_utils_v1',
|
|
||||||
default=False,
|
|
||||||
help=_('Force V1 WMI utility classes')),
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(hyper_opts, 'hyperv')
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_windows_version():
|
|
||||||
return wmi.WMI(moniker='//./root/cimv2').Win32_OperatingSystem()[0].Version
|
|
||||||
|
|
||||||
|
|
||||||
def _check_min_windows_version(major, minor, build=0):
|
|
||||||
version_str = _get_windows_version()
|
|
||||||
return map(int, version_str.split('.')) >= [major, minor, build]
|
|
||||||
|
|
||||||
|
|
||||||
def get_hypervutils():
|
|
||||||
# V1 virtualization namespace features are supported up to
|
|
||||||
# Windows Server / Hyper-V Server 2012
|
|
||||||
# V2 virtualization namespace features are supported starting with
|
|
||||||
# Windows Server / Hyper-V Server 2012
|
|
||||||
# Windows Server / Hyper-V Server 2012 R2 uses the V2 namespace and
|
|
||||||
# introduces additional features
|
|
||||||
|
|
||||||
force_v1_flag = CONF.hyperv.force_hyperv_utils_v1
|
|
||||||
if _check_min_windows_version(6, 3):
|
|
||||||
if force_v1_flag:
|
|
||||||
LOG.warning(_('V1 virtualization namespace no longer supported on '
|
|
||||||
'Windows Server / Hyper-V Server 2012 R2 or above.'))
|
|
||||||
cls = utilsv2.HyperVUtilsV2R2
|
|
||||||
elif not force_v1_flag and _check_min_windows_version(6, 2):
|
|
||||||
cls = utilsv2.HyperVUtilsV2
|
|
||||||
else:
|
|
||||||
cls = utils.HyperVUtils
|
|
||||||
LOG.debug(_("Loading class: %(module_name)s.%(class_name)s"),
|
|
||||||
{'module_name': cls.__module__, 'class_name': cls.__name__})
|
|
||||||
return cls()
|
|
|
@ -1,439 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# @author: Alessandro Pilotti, Cloudbase Solutions Srl
|
|
||||||
# @author: Claudiu Belu, Cloudbase Solutions Srl
|
|
||||||
|
|
||||||
from neutron.plugins.hyperv.agent import utils
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVUtilsV2(utils.HyperVUtils):
|
|
||||||
|
|
||||||
_EXTERNAL_PORT = 'Msvm_ExternalEthernetPort'
|
|
||||||
_ETHERNET_SWITCH_PORT = 'Msvm_EthernetSwitchPort'
|
|
||||||
_PORT_ALLOC_SET_DATA = 'Msvm_EthernetPortAllocationSettingData'
|
|
||||||
_PORT_VLAN_SET_DATA = 'Msvm_EthernetSwitchPortVlanSettingData'
|
|
||||||
_PORT_SECURITY_SET_DATA = 'Msvm_EthernetSwitchPortSecuritySettingData'
|
|
||||||
_PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData'
|
|
||||||
_PORT_EXT_ACL_SET_DATA = _PORT_ALLOC_ACL_SET_DATA
|
|
||||||
_LAN_ENDPOINT = 'Msvm_LANEndpoint'
|
|
||||||
_STATE_DISABLED = 3
|
|
||||||
_OPERATION_MODE_ACCESS = 1
|
|
||||||
|
|
||||||
_VIRTUAL_SYSTEM_SETTING_DATA = 'Msvm_VirtualSystemSettingData'
|
|
||||||
_VM_SUMMARY_ENABLED_STATE = 100
|
|
||||||
_HYPERV_VM_STATE_ENABLED = 2
|
|
||||||
|
|
||||||
_ACL_DIR_IN = 1
|
|
||||||
_ACL_DIR_OUT = 2
|
|
||||||
|
|
||||||
_ACL_TYPE_IPV4 = 2
|
|
||||||
_ACL_TYPE_IPV6 = 3
|
|
||||||
|
|
||||||
_ACL_ACTION_ALLOW = 1
|
|
||||||
_ACL_ACTION_DENY = 2
|
|
||||||
_ACL_ACTION_METER = 3
|
|
||||||
|
|
||||||
_METRIC_ENABLED = 2
|
|
||||||
_NET_IN_METRIC_NAME = 'Filtered Incoming Network Traffic'
|
|
||||||
_NET_OUT_METRIC_NAME = 'Filtered Outgoing Network Traffic'
|
|
||||||
|
|
||||||
_ACL_APPLICABILITY_LOCAL = 1
|
|
||||||
_ACL_APPLICABILITY_REMOTE = 2
|
|
||||||
|
|
||||||
_ACL_DEFAULT = 'ANY'
|
|
||||||
_IPV4_ANY = '0.0.0.0/0'
|
|
||||||
_IPV6_ANY = '::/0'
|
|
||||||
_TCP_PROTOCOL = 'tcp'
|
|
||||||
_UDP_PROTOCOL = 'udp'
|
|
||||||
_ICMP_PROTOCOL = '1'
|
|
||||||
_MAX_WEIGHT = 65500
|
|
||||||
|
|
||||||
# 2 directions x 2 address types = 4 ACLs
|
|
||||||
_REJECT_ACLS_COUNT = 4
|
|
||||||
|
|
||||||
_wmi_namespace = '//./root/virtualization/v2'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(HyperVUtilsV2, self).__init__()
|
|
||||||
|
|
||||||
def connect_vnic_to_vswitch(self, vswitch_name, switch_port_name):
|
|
||||||
vnic = self._get_vnic_settings(switch_port_name)
|
|
||||||
vswitch = self._get_vswitch(vswitch_name)
|
|
||||||
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, True)
|
|
||||||
port.HostResource = [vswitch.path_()]
|
|
||||||
port.Parent = vnic.path_()
|
|
||||||
if not found:
|
|
||||||
vm = self._get_vm_from_res_setting_data(vnic)
|
|
||||||
self._add_virt_resource(vm, port)
|
|
||||||
else:
|
|
||||||
self._modify_virt_resource(port)
|
|
||||||
|
|
||||||
def _modify_virt_resource(self, res_setting_data):
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
(job_path, out_set_data, ret_val) = vs_man_svc.ModifyResourceSettings(
|
|
||||||
ResourceSettings=[res_setting_data.GetText_(1)])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
def _add_virt_resource(self, vm, res_setting_data):
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
(job_path, out_set_data, ret_val) = vs_man_svc.AddResourceSettings(
|
|
||||||
vm.path_(), [res_setting_data.GetText_(1)])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
def _remove_virt_resource(self, res_setting_data):
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
(job, ret_val) = vs_man_svc.RemoveResourceSettings(
|
|
||||||
ResourceSettings=[res_setting_data.path_()])
|
|
||||||
self._check_job_status(ret_val, job)
|
|
||||||
|
|
||||||
def _add_virt_feature(self, element, res_setting_data):
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
(job_path, out_set_data, ret_val) = vs_man_svc.AddFeatureSettings(
|
|
||||||
element.path_(), [res_setting_data.GetText_(1)])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
def _remove_virt_feature(self, feature_resource):
|
|
||||||
self._remove_multiple_virt_features([feature_resource])
|
|
||||||
|
|
||||||
def _remove_multiple_virt_features(self, feature_resources):
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
(job_path, ret_val) = vs_man_svc.RemoveFeatureSettings(
|
|
||||||
FeatureSettings=[f.path_() for f in feature_resources])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
def disconnect_switch_port(
|
|
||||||
self, vswitch_name, switch_port_name, delete_port):
|
|
||||||
"""Disconnects the switch port."""
|
|
||||||
sw_port, found = self._get_switch_port_allocation(switch_port_name)
|
|
||||||
if not sw_port:
|
|
||||||
# Port not found. It happens when the VM was already deleted.
|
|
||||||
return
|
|
||||||
|
|
||||||
if delete_port:
|
|
||||||
self._remove_virt_resource(sw_port)
|
|
||||||
else:
|
|
||||||
sw_port.EnabledState = self._STATE_DISABLED
|
|
||||||
self._modify_virt_resource(sw_port)
|
|
||||||
|
|
||||||
def _get_vswitch(self, vswitch_name):
|
|
||||||
vswitch = self._conn.Msvm_VirtualEthernetSwitch(
|
|
||||||
ElementName=vswitch_name)
|
|
||||||
if not len(vswitch):
|
|
||||||
raise utils.HyperVException(msg=_('VSwitch not found: %s') %
|
|
||||||
vswitch_name)
|
|
||||||
return vswitch[0]
|
|
||||||
|
|
||||||
def _get_vswitch_external_port(self, vswitch):
|
|
||||||
vswitch_ports = vswitch.associators(
|
|
||||||
wmi_result_class=self._ETHERNET_SWITCH_PORT)
|
|
||||||
for vswitch_port in vswitch_ports:
|
|
||||||
lan_endpoints = vswitch_port.associators(
|
|
||||||
wmi_result_class=self._LAN_ENDPOINT)
|
|
||||||
if len(lan_endpoints):
|
|
||||||
lan_endpoints = lan_endpoints[0].associators(
|
|
||||||
wmi_result_class=self._LAN_ENDPOINT)
|
|
||||||
if len(lan_endpoints):
|
|
||||||
ext_port = lan_endpoints[0].associators(
|
|
||||||
wmi_result_class=self._EXTERNAL_PORT)
|
|
||||||
if ext_port:
|
|
||||||
return vswitch_port
|
|
||||||
|
|
||||||
def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name):
|
|
||||||
port_alloc, found = self._get_switch_port_allocation(switch_port_name)
|
|
||||||
if not found:
|
|
||||||
raise utils.HyperVException(
|
|
||||||
msg=_('Port Allocation not found: %s') % switch_port_name)
|
|
||||||
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
vlan_settings = self._get_vlan_setting_data_from_port_alloc(port_alloc)
|
|
||||||
if vlan_settings:
|
|
||||||
# Removing the feature because it cannot be modified
|
|
||||||
# due to a wmi exception.
|
|
||||||
(job_path, ret_val) = vs_man_svc.RemoveFeatureSettings(
|
|
||||||
FeatureSettings=[vlan_settings.path_()])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
(vlan_settings, found) = self._get_vlan_setting_data(switch_port_name)
|
|
||||||
vlan_settings.AccessVlanId = vlan_id
|
|
||||||
vlan_settings.OperationMode = self._OPERATION_MODE_ACCESS
|
|
||||||
(job_path, out, ret_val) = vs_man_svc.AddFeatureSettings(
|
|
||||||
port_alloc.path_(), [vlan_settings.GetText_(1)])
|
|
||||||
self._check_job_status(ret_val, job_path)
|
|
||||||
|
|
||||||
def _get_vlan_setting_data_from_port_alloc(self, port_alloc):
|
|
||||||
return self._get_first_item(port_alloc.associators(
|
|
||||||
wmi_result_class=self._PORT_VLAN_SET_DATA))
|
|
||||||
|
|
||||||
def _get_vlan_setting_data(self, switch_port_name, create=True):
|
|
||||||
return self._get_setting_data(
|
|
||||||
self._PORT_VLAN_SET_DATA,
|
|
||||||
switch_port_name, create)
|
|
||||||
|
|
||||||
def _get_switch_port_allocation(self, switch_port_name, create=False):
|
|
||||||
return self._get_setting_data(
|
|
||||||
self._PORT_ALLOC_SET_DATA,
|
|
||||||
switch_port_name, create)
|
|
||||||
|
|
||||||
def _get_setting_data(self, class_name, element_name, create=True):
|
|
||||||
element_name = element_name.replace("'", '"')
|
|
||||||
q = self._conn.query("SELECT * FROM %(class_name)s WHERE "
|
|
||||||
"ElementName = '%(element_name)s'" %
|
|
||||||
{"class_name": class_name,
|
|
||||||
"element_name": element_name})
|
|
||||||
data = self._get_first_item(q)
|
|
||||||
found = data is not None
|
|
||||||
if not data and create:
|
|
||||||
data = self._get_default_setting_data(class_name)
|
|
||||||
data.ElementName = element_name
|
|
||||||
return data, found
|
|
||||||
|
|
||||||
def _get_default_setting_data(self, class_name):
|
|
||||||
return self._conn.query("SELECT * FROM %s WHERE InstanceID "
|
|
||||||
"LIKE '%%\\Default'" % class_name)[0]
|
|
||||||
|
|
||||||
def _get_first_item(self, obj):
|
|
||||||
if obj:
|
|
||||||
return obj[0]
|
|
||||||
|
|
||||||
def enable_port_metrics_collection(self, switch_port_name):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add the ACLs only if they don't already exist
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_ALLOC_ACL_SET_DATA)
|
|
||||||
for acl_type in [self._ACL_TYPE_IPV4, self._ACL_TYPE_IPV6]:
|
|
||||||
for acl_dir in [self._ACL_DIR_IN, self._ACL_DIR_OUT]:
|
|
||||||
_acls = self._filter_acls(
|
|
||||||
acls, self._ACL_ACTION_METER, acl_dir, acl_type)
|
|
||||||
|
|
||||||
if not _acls:
|
|
||||||
acl = self._create_acl(
|
|
||||||
acl_dir, acl_type, self._ACL_ACTION_METER)
|
|
||||||
self._add_virt_feature(port, acl)
|
|
||||||
|
|
||||||
def enable_control_metrics(self, switch_port_name):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
return
|
|
||||||
|
|
||||||
metric_svc = self._conn.Msvm_MetricService()[0]
|
|
||||||
metric_names = [self._NET_IN_METRIC_NAME, self._NET_OUT_METRIC_NAME]
|
|
||||||
|
|
||||||
for metric_name in metric_names:
|
|
||||||
metric_def = self._conn.CIM_BaseMetricDefinition(Name=metric_name)
|
|
||||||
if metric_def:
|
|
||||||
metric_svc.ControlMetrics(
|
|
||||||
Subject=port.path_(),
|
|
||||||
Definition=metric_def[0].path_(),
|
|
||||||
MetricCollectionEnabled=self._METRIC_ENABLED)
|
|
||||||
|
|
||||||
def can_enable_control_metrics(self, switch_port_name):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not self._is_port_vm_started(port):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# all 4 meter ACLs must be existent first. (2 x direction)
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_ALLOC_ACL_SET_DATA)
|
|
||||||
acls = [a for a in acls if a.Action == self._ACL_ACTION_METER]
|
|
||||||
if len(acls) < 2:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _is_port_vm_started(self, port):
|
|
||||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
||||||
vmsettings = port.associators(
|
|
||||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA)
|
|
||||||
#See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
|
|
||||||
(ret_val, summary_info) = vs_man_svc.GetSummaryInformation(
|
|
||||||
[self._VM_SUMMARY_ENABLED_STATE],
|
|
||||||
[v.path_() for v in vmsettings])
|
|
||||||
if ret_val or not summary_info:
|
|
||||||
raise utils.HyperVException(msg=_('Cannot get VM summary data '
|
|
||||||
'for: %s') % port.ElementName)
|
|
||||||
|
|
||||||
return summary_info[0].EnabledState is self._HYPERV_VM_STATE_ENABLED
|
|
||||||
|
|
||||||
def create_security_rule(self, switch_port_name, direction, acl_type,
|
|
||||||
local_port, protocol, remote_address):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add the ACLs only if they don't already exist
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA)
|
|
||||||
weight = self._get_new_weight(acls)
|
|
||||||
self._bind_security_rule(
|
|
||||||
port, direction, acl_type, self._ACL_ACTION_ALLOW, local_port,
|
|
||||||
protocol, remote_address, weight)
|
|
||||||
|
|
||||||
def remove_security_rule(self, switch_port_name, direction, acl_type,
|
|
||||||
local_port, protocol, remote_address):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
# Port not found. It happens when the VM was already deleted.
|
|
||||||
return
|
|
||||||
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA)
|
|
||||||
filtered_acls = self._filter_security_acls(
|
|
||||||
acls, self._ACL_ACTION_ALLOW, direction, acl_type, local_port,
|
|
||||||
protocol, remote_address)
|
|
||||||
|
|
||||||
for acl in filtered_acls:
|
|
||||||
self._remove_virt_feature(acl)
|
|
||||||
|
|
||||||
def remove_all_security_rules(self, switch_port_name):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
# Port not found. It happens when the VM was already deleted.
|
|
||||||
return
|
|
||||||
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA)
|
|
||||||
filtered_acls = [a for a in acls if
|
|
||||||
a.Action is not self._ACL_ACTION_METER]
|
|
||||||
|
|
||||||
if filtered_acls:
|
|
||||||
self._remove_multiple_virt_features(filtered_acls)
|
|
||||||
|
|
||||||
def create_default_reject_all_rules(self, switch_port_name):
|
|
||||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
||||||
if not found:
|
|
||||||
raise utils.HyperVException(
|
|
||||||
msg=_('Port Allocation not found: %s') % switch_port_name)
|
|
||||||
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA)
|
|
||||||
filtered_acls = [v for v in acls if v.Action == self._ACL_ACTION_DENY]
|
|
||||||
|
|
||||||
if len(filtered_acls) >= self._REJECT_ACLS_COUNT:
|
|
||||||
return
|
|
||||||
|
|
||||||
for acl in filtered_acls:
|
|
||||||
self._remove_virt_feature(acl)
|
|
||||||
|
|
||||||
weight = 0
|
|
||||||
ipv4_pair = (self._ACL_TYPE_IPV4, self._IPV4_ANY)
|
|
||||||
ipv6_pair = (self._ACL_TYPE_IPV6, self._IPV6_ANY)
|
|
||||||
for direction in [self._ACL_DIR_IN, self._ACL_DIR_OUT]:
|
|
||||||
for acl_type, address in [ipv4_pair, ipv6_pair]:
|
|
||||||
for protocol in [self._TCP_PROTOCOL,
|
|
||||||
self._UDP_PROTOCOL,
|
|
||||||
self._ICMP_PROTOCOL]:
|
|
||||||
self._bind_security_rule(
|
|
||||||
port, direction, acl_type, self._ACL_ACTION_DENY,
|
|
||||||
self._ACL_DEFAULT, protocol, address, weight)
|
|
||||||
weight += 1
|
|
||||||
|
|
||||||
def _bind_security_rule(self, port, direction, acl_type, action,
|
|
||||||
local_port, protocol, remote_address, weight):
|
|
||||||
acls = port.associators(wmi_result_class=self._PORT_EXT_ACL_SET_DATA)
|
|
||||||
filtered_acls = self._filter_security_acls(
|
|
||||||
acls, action, direction, acl_type, local_port, protocol,
|
|
||||||
remote_address)
|
|
||||||
|
|
||||||
for acl in filtered_acls:
|
|
||||||
self._remove_virt_feature(acl)
|
|
||||||
|
|
||||||
acl = self._create_security_acl(
|
|
||||||
direction, acl_type, action, local_port, protocol, remote_address,
|
|
||||||
weight)
|
|
||||||
|
|
||||||
self._add_virt_feature(port, acl)
|
|
||||||
|
|
||||||
def _create_acl(self, direction, acl_type, action):
|
|
||||||
acl = self._get_default_setting_data(self._PORT_ALLOC_ACL_SET_DATA)
|
|
||||||
acl.set(Direction=direction,
|
|
||||||
AclType=acl_type,
|
|
||||||
Action=action,
|
|
||||||
Applicability=self._ACL_APPLICABILITY_LOCAL)
|
|
||||||
return acl
|
|
||||||
|
|
||||||
def _create_security_acl(self, direction, acl_type, action, local_port,
|
|
||||||
protocol, remote_ip_address, weight):
|
|
||||||
acl = self._create_acl(direction, acl_type, action)
|
|
||||||
(remote_address, remote_prefix_length) = remote_ip_address.split('/')
|
|
||||||
acl.set(Applicability=self._ACL_APPLICABILITY_REMOTE,
|
|
||||||
RemoteAddress=remote_address,
|
|
||||||
RemoteAddressPrefixLength=remote_prefix_length)
|
|
||||||
return acl
|
|
||||||
|
|
||||||
def _filter_acls(self, acls, action, direction, acl_type, remote_addr=""):
|
|
||||||
return [v for v in acls
|
|
||||||
if v.Action == action and
|
|
||||||
v.Direction == direction and
|
|
||||||
v.AclType == acl_type and
|
|
||||||
v.RemoteAddress == remote_addr]
|
|
||||||
|
|
||||||
def _filter_security_acls(self, acls, acl_action, direction, acl_type,
|
|
||||||
local_port, protocol, remote_addr=""):
|
|
||||||
(remote_address, remote_prefix_length) = remote_addr.split('/')
|
|
||||||
remote_prefix_length = int(remote_prefix_length)
|
|
||||||
|
|
||||||
return [v for v in acls
|
|
||||||
if v.Direction == direction and
|
|
||||||
v.Action in [self._ACL_ACTION_ALLOW, self._ACL_ACTION_DENY] and
|
|
||||||
v.AclType == acl_type and
|
|
||||||
v.RemoteAddress == remote_address and
|
|
||||||
v.RemoteAddressPrefixLength == remote_prefix_length]
|
|
||||||
|
|
||||||
def _get_new_weight(self, acls):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class HyperVUtilsV2R2(HyperVUtilsV2):
|
|
||||||
_PORT_EXT_ACL_SET_DATA = 'Msvm_EthernetSwitchPortExtendedAclSettingData'
|
|
||||||
_MAX_WEIGHT = 65500
|
|
||||||
|
|
||||||
# 2 directions x 2 address types x 3 protocols = 12 ACLs
|
|
||||||
_REJECT_ACLS_COUNT = 12
|
|
||||||
|
|
||||||
def _create_security_acl(self, direction, acl_type, action, local_port,
|
|
||||||
protocol, remote_addr, weight):
|
|
||||||
acl = self._get_default_setting_data(self._PORT_EXT_ACL_SET_DATA)
|
|
||||||
acl.set(Direction=direction,
|
|
||||||
Action=action,
|
|
||||||
LocalPort=str(local_port),
|
|
||||||
Protocol=protocol,
|
|
||||||
RemoteIPAddress=remote_addr,
|
|
||||||
IdleSessionTimeout=0,
|
|
||||||
Weight=weight)
|
|
||||||
return acl
|
|
||||||
|
|
||||||
def _filter_security_acls(self, acls, action, direction, acl_type,
|
|
||||||
local_port, protocol, remote_addr=""):
|
|
||||||
return [v for v in acls
|
|
||||||
if v.Action == action and
|
|
||||||
v.Direction == direction and
|
|
||||||
v.LocalPort == str(local_port) and
|
|
||||||
v.Protocol == protocol and
|
|
||||||
v.RemoteIPAddress == remote_addr]
|
|
||||||
|
|
||||||
def _get_new_weight(self, acls):
|
|
||||||
acls = [a for a in acls if a.Action is not self._ACL_ACTION_DENY]
|
|
||||||
if not acls:
|
|
||||||
return self._MAX_WEIGHT - 1
|
|
||||||
|
|
||||||
weights = [a.Weight for a in acls]
|
|
||||||
min_weight = min(weights)
|
|
||||||
for weight in range(min_weight, self._MAX_WEIGHT):
|
|
||||||
if weight not in weights:
|
|
||||||
return weight
|
|
||||||
|
|
||||||
return min_weight - 1
|
|
|
@ -1,80 +0,0 @@
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 Cloudbase Solutions SRL
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# @author: Alessandro Pilotti, Cloudbase Solutions Srl
|
|
||||||
|
|
||||||
from neutron.common import rpc_compat
|
|
||||||
from neutron.common import topics
|
|
||||||
from neutron.openstack.common import log as logging
|
|
||||||
from neutron.plugins.hyperv.common import constants
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentNotifierApi(rpc_compat.RpcProxy):
|
|
||||||
'''Agent side of the openvswitch rpc API.
|
|
||||||
|
|
||||||
API version history:
|
|
||||||
1.0 - Initial version.
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
BASE_RPC_API_VERSION = '1.0'
|
|
||||||
|
|
||||||
def __init__(self, topic):
|
|
||||||
super(AgentNotifierApi, self).__init__(
|
|
||||||
topic=topic, default_version=self.BASE_RPC_API_VERSION)
|
|
||||||
self.topic_network_delete = topics.get_topic_name(topic,
|
|
||||||
topics.NETWORK,
|
|
||||||
topics.DELETE)
|
|
||||||
self.topic_port_update = topics.get_topic_name(topic,
|
|
||||||
topics.PORT,
|
|
||||||
topics.UPDATE)
|
|
||||||
self.topic_port_delete = topics.get_topic_name(topic,
|
|
||||||
topics.PORT,
|
|
||||||
topics.DELETE)
|
|
||||||
self.topic_tunnel_update = topics.get_topic_name(topic,
|
|
||||||
constants.TUNNEL,
|
|
||||||
topics.UPDATE)
|
|
||||||
|
|
||||||
def network_delete(self, context, network_id):
|
|
||||||
self.fanout_cast(context,
|
|
||||||
self.make_msg('network_delete',
|
|
||||||
network_id=network_id),
|
|
||||||
topic=self.topic_network_delete)
|
|
||||||
|
|
||||||
def port_update(self, context, port, network_type, segmentation_id,
|
|
||||||
physical_network):
|
|
||||||
self.fanout_cast(context,
|
|
||||||
self.make_msg('port_update',
|
|
||||||
port=port,
|
|
||||||
network_type=network_type,
|
|
||||||
segmentation_id=segmentation_id,
|
|
||||||
physical_network=physical_network),
|
|
||||||
topic=self.topic_port_update)
|
|
||||||
|
|
||||||
def port_delete(self, context, port_id):
|
|
||||||
self.fanout_cast(context,
|
|
||||||
self.make_msg('port_delete',
|
|
||||||
port_id=port_id),
|
|
||||||
topic=self.topic_port_delete)
|
|
||||||
|
|
||||||
def tunnel_update(self, context, tunnel_ip, tunnel_id):
|
|
||||||
self.fanout_cast(context,
|
|
||||||
self.make_msg('tunnel_update',
|
|
||||||
tunnel_ip=tunnel_ip,
|
|
||||||
tunnel_id=tunnel_id),
|
|
||||||
topic=self.topic_tunnel_update)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue