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