blob: c5cc5144c6d97e29941e0e64071c9b4ece23d0b3 [file] [log] [blame]
# Copyright 2017-present Open Networking Foundation
#
# 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.
"""
Test the Nicira controller role extension
"""
import struct
import unittest
import logging
import oftest
from oftest import config
import oftest.controller as controller
import ofp
import oftest.base_tests as base_tests
from oftest.testutils import *
NX_ROLE_OTHER = 0
NX_ROLE_MASTER = 1
NX_ROLE_SLAVE = 2
def set_role(test, role, con=None):
if con == None:
con = test.controller
request = ofp.message.nicira_controller_role_request(role=role)
response, _ = con.transact(request)
test.assertTrue(isinstance(response, ofp.message.nicira_controller_role_reply), "Expected a role reply")
test.assertEquals(response.role, role)
class AnyReply(base_tests.SimpleDataPlane):
"""
Verify that a role request gets either a role reply or an error.
This test should pass on any switch, no matter whether it implements
the extension.
"""
def runTest(self):
request = ofp.message.nicira_controller_role_request(role=NX_ROLE_MASTER)
response, pkt = self.controller.transact(request)
self.assertTrue(response is not None, "No reply to Nicira role request")
if isinstance(response, ofp.message.nicira_controller_role_reply):
logging.info("Role reply received")
logging.info(response.show())
self.assertEquals(response.role, NX_ROLE_MASTER)
elif isinstance(response, ofp.message.bad_request_error_msg):
logging.info("Error message received")
logging.info(response.show())
self.assertEquals(response.code, ofp.OFPBRC_BAD_VENDOR)
else:
raise AssertionError("Unexpected reply type")
@nonstandard
class RolePermissions(base_tests.SimpleDataPlane):
"""
Verify that a slave connection cannot modify switch state, but
a master or equal can.
"""
def runTest(self):
self.features_reply, _ = self.controller.transact(ofp.message.features_request())
delete_all_flows(self.controller)
self.verify_permission(True)
set_role(self, NX_ROLE_MASTER)
self.verify_permission(True)
set_role(self, NX_ROLE_SLAVE)
self.verify_permission(False)
set_role(self, NX_ROLE_OTHER)
self.verify_permission(True)
def verify_permission(self, perm):
port = self.features_reply.ports[0]
self.controller.message_send(ofp.message.port_mod(port_no=port.port_no, hw_addr=port.hw_addr))
self.controller.message_send(ofp.message.packet_out(buffer_id=0xffffffff))
self.controller.message_send(ofp.message.flow_add(buffer_id=0xffffffff))
do_barrier(self.controller)
err_count = 0
while self.controller.packets:
msg = self.controller.packets.pop(0)[0]
if msg.type == ofp.OFPT_ERROR:
self.assertEquals(msg.err_type, ofp.OFPET_BAD_REQUEST)
self.assertEquals(msg.code, ofp.OFPBRC_EPERM)
err_count += 1
if perm:
self.assertEquals(err_count, 0, "Expected no errors")
else:
self.assertEquals(err_count, 3, "Expected errors for each message")
@nonstandard
class SlaveNoPacketIn(base_tests.SimpleDataPlane):
"""
Verify that slave connections do not receive OFPT_PACKET_IN messages but other roles do.
"""
def runTest(self):
delete_all_flows(self.controller)
ingress_port = config["port_map"].keys()[0]
pkt = str(simple_tcp_packet())
set_role(self, NX_ROLE_MASTER)
self.dataplane.send(ingress_port, pkt)
verify_packet_in(self, pkt, ingress_port, ofp.OFPR_NO_MATCH)
set_role(self, NX_ROLE_SLAVE)
self.dataplane.send(ingress_port, pkt)
verify_no_packet_in(self, pkt, ingress_port)
set_role(self, NX_ROLE_OTHER)
self.dataplane.send(ingress_port, pkt)
verify_packet_in(self, pkt, ingress_port, ofp.OFPR_NO_MATCH)
@nonstandard
@disabled
class RoleSwitch(unittest.TestCase):
"""
Verify that when a connection becomes a master the existing master is
downgraded to slave.
Requires the switch to attempt to connect in parallel to ports 6633
and 6634 on the configured IP.
"""
def setUp(self):
host = config["controller_host"]
self.controllers = [
controller.Controller(host=host,port=6633),
controller.Controller(host=host,port=6634)
]
def runTest(self):
# Connect and handshake with both controllers
for con in self.controllers:
con.start()
if not con.connect():
raise AssertionError("failed to connect controller %s" % str(con))
reply, _ = con.transact(ofp.message.features_request())
self.assertTrue(isinstance(reply, ofp.message.features_reply))
# Initial role assignment, controller 0 is master
set_role(self, NX_ROLE_MASTER, con=self.controllers[0])
set_role(self, NX_ROLE_SLAVE, con=self.controllers[1])
self.verify_role(self.controllers[0], True)
self.verify_role(self.controllers[1], False)
# Controller 1 becomes master
set_role(self, NX_ROLE_MASTER, con=self.controllers[1])
self.verify_role(self.controllers[0], False)
self.verify_role(self.controllers[1], True)
# Controller 0 becomes master
set_role(self, NX_ROLE_MASTER, con=self.controllers[0])
self.verify_role(self.controllers[0], True)
self.verify_role(self.controllers[1], False)
# Controller 1 becomes equal
set_role(self, NX_ROLE_OTHER, con=self.controllers[1])
self.verify_role(self.controllers[0], True)
self.verify_role(self.controllers[1], True)
# Both controllers become slaves
set_role(self, NX_ROLE_SLAVE, con=self.controllers[0])
set_role(self, NX_ROLE_SLAVE, con=self.controllers[1])
self.verify_role(self.controllers[0], False)
self.verify_role(self.controllers[1], False)
def verify_role(self, con, master):
con.message_send(ofp.message.flow_add(buffer_id=0xffffffff))
do_barrier(con)
err_count = 0
while con.packets:
msg = con.packets.pop(0)[0]
if msg.type == ofp.OFPT_ERROR:
self.assertEquals(msg.err_type, ofp.OFPET_BAD_REQUEST)
self.assertEquals(msg.code, ofp.OFPBRC_EPERM)
err_count += 1
if master:
self.assertEquals(err_count, 0, "Expected no errors")
else:
self.assertEquals(err_count, 1, "Expected errors for each message")
def tearDown(self):
for con in self.controllers:
con.shutdown()
@nonstandard
@disabled
class EqualAsyncMessages(unittest.TestCase):
"""
Verify that 'equal' controllers all get async events.
Requires the switch to attempt to connect in parallel to ports 6633
and 6634 on the configured IP.
"""
def setUp(self):
host = config["controller_host"]
self.controllers = [
controller.Controller(host=host,port=6633),
controller.Controller(host=host,port=6634)
]
self.dataplane = oftest.dataplane_instance
self.dataplane.flush()
def runTest(self):
# Connect and handshake with both controllers
for con in self.controllers:
con.start()
if not con.connect():
raise AssertionError("failed to connect controller %s" % str(con))
reply, _ = con.transact(ofp.message.features_request())
self.assertTrue(isinstance(reply, ofp.message.features_reply))
delete_all_flows(self.controllers[0])
do_barrier(self.controllers[0])
pkt = str(simple_tcp_packet())
ingress_port = config["port_map"].keys()[0]
self.dataplane.send(ingress_port, pkt)
for con in self.controllers:
verify_packet_in(self, pkt, ingress_port, ofp.OFPR_NO_MATCH, controller=con)
def tearDown(self):
for con in self.controllers:
con.shutdown()
con.join()
del self.controllers