def test_apply_networking(switch, network, fresh_database): '''Test to validate apply_networking commits actions incrementally This test verifies that the apply_networking() function in hil/deferred.py incrementally commits actions, which ensures that any error on an action will not require a complete rerun of the prior actions (e.g. if an error is thrown on the 3rd action, the 1st and 2nd action will have already been committed) The test also verifies that if a new networking action fails, then the old networking actions in the queue were commited. ''' nic = [] actions = [] # initialize 3 nics and networking actions for i in range(0, 3): interface = 'gi1/0/%d' % (i) nic.append(new_nic(str(i))) nic[i].port = model.Port(label=interface, switch=switch) actions.append( model.NetworkingAction(nic=nic[i], new_network=network, channel='vlan/native', type='modify_port')) # this makes the last action invalid for the test switch because the switch # raises an error when the networking action is of type revert port. actions[2] = model.NetworkingAction(nic=nic[2], new_network=None, channel='', type='revert_port') for action in actions: db.session.add(action) db.session.commit() with pytest.raises(RevertPortError): deferred.apply_networking() # close the session opened by `apply_networking` when `handle_actions` # fails; without this the tests would just stall (when using postgres) db.session.close() local_db = new_db() pending_action = local_db.session \ .query(model.NetworkingAction) \ .order_by(model.NetworkingAction.id).one_or_none() current_count = local_db.session \ .query(model.NetworkingAction).count() local_db.session.delete(pending_action) local_db.session.commit() local_db.session.close() # test that there's only pending action in the queue and it is of type # revert_port assert current_count == 1 assert pending_action.type == 'revert_port'
def node_detach_network(node, nic, network): """Detach network ``network`` from physical nic ``nic``. Raises ProjectMismatchError if the node is not in a project. Raises BlockedError if there is already a pending network action. Raises BadArgumentError if the network is not attached to the nic. """ auth_backend = get_auth_backend() node = _must_find(model.Node, node) network = _must_find(model.Network, network) nic = _must_find_n(node, model.Nic, nic) if not node.project: raise errors.ProjectMismatchError("Node not in project") auth_backend.require_project_access(node.project) if nic.current_action: raise errors.BlockedError( "A networking operation is already active on the nic.") attachment = model.NetworkAttachment.query \ .filter_by(nic=nic, network=network).first() if attachment is None: raise errors.BadArgumentError( "The network is not attached to the nic.") db.session.add( model.NetworkingAction(type='modify_port', nic=nic, channel=attachment.channel, new_network=None)) db.session.commit() return '', 202
def port_revert(switch, port): get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) port = _must_find_n(switch, model.Port, port) if port.nic is None: raise errors.NotFoundError(port.label + " not attached") if port.nic.current_action: raise errors.BlockedError("Port already has a pending action.") db.session.add( model.NetworkingAction(type='revert_port', nic=port.nic, channel='', new_network=None)) db.session.commit()
def node_detach_network(node, nic, network): """Detach network ``network`` from physical nic ``nic``. Raises ProjectMismatchError if the node is not in a project. Raises BlockedError if there is already a pending network action. Raises BadArgumentError if the network is not attached to the nic. """ auth_backend = get_auth_backend() node = get_or_404(model.Node, node) network = get_or_404(model.Network, network) nic = get_child_or_404(node, model.Nic, nic) if not node.project: raise errors.ProjectMismatchError("Node not in project") auth_backend.require_project_access(node.project) check_pending_action(nic) attachment = model.NetworkAttachment.query \ .filter_by(nic=nic, network=network).one_or_none() if attachment is None: raise errors.BadArgumentError( "The network is not attached to the nic.") switch = nic.port.owner switch.ensure_legal_operation(nic, 'detach', attachment.channel) unique_id = str(uuid.uuid4()) db.session.add( model.NetworkingAction(type='modify_port', nic=nic, channel=attachment.channel, uuid=unique_id, status='PENDING', new_network=None)) db.session.commit() return json.dumps({'status_id': unique_id}), 202
def port_revert(switch, port): """Detach the port from all networks.""" get_auth_backend().require_admin() switch = get_or_404(model.Switch, switch) port = get_child_or_404(switch, model.Port, port) if port.nic is None: raise errors.NotFoundError(port.label + " not attached") check_pending_action(port.nic) unique_id = str(uuid.uuid4()) action = model.NetworkingAction(type='revert_port', nic=port.nic, channel='', uuid=unique_id, status='PENDING', new_network=None) db.session.add(action) db.session.commit() return json.dumps({'status_id': unique_id})
def test_modify_port(self, switch, nic, network): """Test the modify_port method""" # Create a port on the switch and connect it to the nic port = model.Port(label=INTERFACE1, switch=switch) port.nic = nic # Test action to set a network as native action_native = model.NetworkingAction(type='modify_port', nic=nic, new_network=network, channel='vlan/native') with requests_mock.mock() as mock: url_switch = switch._construct_url(INTERFACE1) mock.post(url_switch) url_mode = switch._construct_url(INTERFACE1, suffix='mode') mock.put(url_mode) url_tag = switch._construct_url(INTERFACE1, suffix='trunk/tag/native-vlan') mock.delete(url_tag) url_trunk = switch._construct_url(INTERFACE1, suffix='trunk') mock.put(url_trunk) switch.modify_port(action_native.nic.port.label, action_native.channel, action_native.new_network.network_id) assert mock.called assert mock.call_count == 4 assert mock.request_history[0].text == SWITCHPORT_PAYLOAD assert mock.request_history[1].text == TRUNK_PAYLOAD assert mock.request_history[3].text == TRUNK_NATIVE_PAYLOAD # Test action to remove a native network action_rm_native = model.NetworkingAction(type='modify_port', nic=nic, new_network=None, channel='vlan/native') with requests_mock.mock() as mock: url_native = switch._construct_url(INTERFACE1, suffix='trunk/native-vlan') mock.delete(url_native) switch.modify_port(action_rm_native.nic.port.label, action_rm_native.channel, None) assert mock.called assert mock.call_count == 1 # Test action to add a vlan action_vlan = model.NetworkingAction(type='modify_port', nic=nic, new_network=network, channel='vlan/102') with requests_mock.mock() as mock: url_switch = switch._construct_url(INTERFACE1) mock.post(url_switch) url_mode = switch._construct_url(INTERFACE1, suffix='mode') mock.put(url_mode) url_trunk = switch._construct_url(INTERFACE1, suffix='trunk/allowed/vlan') mock.put(url_trunk) switch.modify_port(action_vlan.nic.port.label, action_vlan.channel, action_vlan.new_network.network_id) assert mock.called assert mock.call_count == 3 assert mock.request_history[0].text == SWITCHPORT_PAYLOAD assert mock.request_history[1].text == TRUNK_PAYLOAD assert mock.request_history[2].text == TRUNK_VLAN_PAYLOAD # Test action to remove a vlan action_rm_vlan = model.NetworkingAction(type='modify_port', nic=nic, new_network=None, channel='vlan/102') with requests_mock.mock() as mock: url_trunk = switch._construct_url(INTERFACE1, suffix='trunk/allowed/vlan') mock.put(url_trunk) switch.modify_port(action_rm_vlan.nic.port.label, action_rm_vlan.channel, None) assert mock.called assert mock.call_count == 1 assert mock.request_history[0].text == TRUNK_REMOVE_VLAN_PAYLOAD
def node_connect_network(node, nic, network, channel=None): """Connect a physical NIC to a network, on channel. If channel is ``None``, use the allocator default. Raises ProjectMismatchError if the node is not in a project, or if the project does not have access rights to the given network. Raises BlockedError if there is a pending network action, or if the network is already attached to the nic, or if the channel is in use. Raises BadArgumentError if the channel is invalid for the network. """ def _have_attachment(nic, query): """Return whether there are any attachments matching ``query`` for ``nic``. ``query`` should an argument suitable to pass to db.query(...).filter """ return model.NetworkAttachment.query.filter( model.NetworkAttachment.nic == nic, query, ).first() is not None auth_backend = get_auth_backend() node = get_or_404(model.Node, node) nic = get_child_or_404(node, model.Nic, nic) network = get_or_404(model.Network, network) if not node.project: raise errors.ProjectMismatchError("Node not in project") auth_backend.require_project_access(node.project) project = node.project allocator = get_network_allocator() if nic.port is None: raise errors.NotFoundError("No port is connected to given nic.") check_pending_action(nic) if (network.access) and (project not in network.access): raise errors.ProjectMismatchError( "Project does not have access to given network.") if _have_attachment(nic, model.NetworkAttachment.network == network): raise errors.BlockedError( "The network is already attached to the nic.") if channel is None: channel = allocator.get_default_channel() if _have_attachment(nic, model.NetworkAttachment.channel == channel): raise errors.BlockedError("The channel is already in use on the nic.") if not allocator.is_legal_channel_for(channel, network.network_id): raise errors.BadArgumentError( "Channel %r, is not legal for this network." % channel) switch = nic.port.owner switch.ensure_legal_operation(nic, 'connect', channel) unique_id = str(uuid.uuid4()) db.session.add( model.NetworkingAction(type='modify_port', nic=nic, new_network=network, channel=channel, uuid=unique_id, status='PENDING')) db.session.commit() return json.dumps({'status_id': unique_id}), 202
def test_apply_networking(switch, network, fresh_database): '''Test to validate apply_networking commits actions incrementally This test verifies that the apply_networking() function in hil/deferred.py incrementally commits actions, which ensures that any error on an action will not require a complete rerun of the prior actions (e.g. if an error is thrown on the 3rd action, the 1st and 2nd action will have already been committed) The test also verifies that if a new networking action fails, then the old networking actions in the queue were commited. ''' nic = [] actions = [] # initialize 3 nics and networking actions for i in range(0, 2): interface = 'gi1/0/%d' % (i) nic.append(new_nic(str(i))) nic[i].port = model.Port(label=interface, switch=switch) unique_id = str(uuid.uuid4()) actions.append( model.NetworkingAction(nic=nic[i], new_network=network, channel='vlan/native', type='modify_port', uuid=unique_id, status='PENDING')) # Create another aciton of type revert_port. This action is invalid for the # test switch because the switch raises an error when the networking action # is of type revert port. unique_id = str(uuid.uuid4()) nic.append(new_nic('2')) nic[2].port = model.Port(label=interface, switch=switch) actions.append( model.NetworkingAction(nic=nic[2], new_network=None, uuid=unique_id, channel='', status='PENDING', type='revert_port')) # get some nic attributes before we close this db session. nic2_label = nic[2].label nic2_node = nic[2].owner.label for action in actions: db.session.add(action) db.session.commit() # simple check to ensure that right number of actions are added. total_count = db.session.query(model.NetworkingAction).count() assert total_count == 3 deferred.apply_networking() # close the session opened by `apply_networking` when `handle_actions` # fails; without this the tests would just stall (when using postgres) db.session.close() local_db = new_db() errored_action = local_db.session \ .query(model.NetworkingAction) \ .order_by(model.NetworkingAction.id).filter_by(status='ERROR') \ .one_or_none() # Count the number of actions with different statuses error_count = local_db.session \ .query(model.NetworkingAction).filter_by(status='ERROR').count() pending_count = local_db.session \ .query(model.NetworkingAction).filter_by(status='PENDING').count() done_count = local_db.session \ .query(model.NetworkingAction).filter_by(status='DONE').count() # test that there's only 1 action that errored out in the queue and that it # is of type revert_port assert error_count == 1 assert errored_action.type == 'revert_port' assert pending_count == 0 assert done_count == 2 local_db.session.commit() local_db.session.close() # add another action on a nic with a previously failed action. api.network_create('corsair', 'admin', '', '105') api.node_connect_network(nic2_node, nic2_label, 'corsair') # the api call should delete the errored action on that nic, and a new # pending action should appear. local_db = new_db() errored_action = local_db.session \ .query(model.NetworkingAction) \ .order_by(model.NetworkingAction.id).filter_by(status='ERROR') \ .one_or_none() pending_action = local_db.session \ .query(model.NetworkingAction) \ .order_by(model.NetworkingAction.id).filter_by(status='PENDING') \ .one_or_none() assert errored_action is None assert pending_action is not None local_db.session.commit() local_db.session.close()
def create_bigint_db(): """Create database objects used in 'after-PK-bigint.sql'""" from hil.ext.switches.n3000 import DellN3000 from hil.ext.switches.dell import PowerConnect55xx from hil.ext.switches.brocade import Brocade from hil.ext.switches.nexus import Nexus from hil.ext.switches.mock import MockSwitch from hil.ext.obm.ipmi import Ipmi from hil.ext.obm.mock import MockObm from hil.ext.auth.database import User from hil.ext.auth import database as dbauth with app.app_context(): db.session.add( DellN3000(label='sw-n3000', hostname='host', username='******', password='******', dummy_vlan='5', type=DellN3000.api_name)) dell1 = PowerConnect55xx(label='sw-dell', hostname='host', username='******', password='******', type=PowerConnect55xx.api_name) db.session.add(dell1) db.session.add( Nexus(label='sw-nexus', hostname='host', username='******', password='******', dummy_vlan='5', type=Nexus.api_name)) db.session.add( Brocade(label='sw-brocade', hostname='host', username='******', password='******', interface_type='4', type=Brocade.api_name)) db.session.add( MockSwitch(label='sw0', hostname='host', username='******', password='******', type=MockSwitch.api_name)) proj = model.Project(label='runway') db.session.add(proj) headnode1 = model.Headnode(label='runway_headnode', project=proj, base_img='image1') db.session.add(headnode1) db.session.add(model.Hnic(label='hnic1', headnode=headnode1)) ipmi = Ipmi(host='host', user='******', password='******') db.session.add(ipmi) mock_obm = MockObm(host='host', user='******', password='******') db.session.add(mock_obm) node1 = model.Node(label='node-1', obm=ipmi) db.session.add(node1) db.session.add( model.Metadata(label='meta', value="it is a true value", node=node1)) network1 = model.Network(owner=None, access=[proj], allocated=False, network_id="networking network", label='hil wireless') db.session.add(network1) nic1 = model.Nic(node=node1, label='pxe', mac_addr='ff:ff:ff:ff:ff:fe') model.Port(label='A fine port', switch=dell1) db.session.add(nic1) db.session.add( model.NetworkAttachment(nic=nic1, network_id=1, channel='vlan/100')) db.session.add( model.NetworkingAction(type='modify_port', nic=nic1, new_network=network1, channel='vlan/100')) jim = User(label='jim', password='******', is_admin=True) db.session.add(jim) local.auth = dbauth.User.query.filter_by(label='jim').one() dbauth.user_add_project('jim', 'runway') db.session.commit() # Original password is "pass" db.session.query(User).get(1).hashed_password = \ ('$6$rounds=656000$iTyrApYTUhMx4b4g$YcaMExV' 'YtS0ut2yXWrT64OggFpE4lLg12QsAuyMA3YKX6Czth' 'XeisA47dJZW9GwU2q2CTIVrsbpxAVT64Pih2/') db.session.commit()
def node_connect_network(node, nic, network, channel=None): """Connect a physical NIC to a network, on channel. If channel is ``None``, use the allocator default. Raises ProjectMismatchError if the node is not in a project, or if the project does not have access rights to the given network. Raises BlockedError if there is a pending network action, or if the network is already attached to the nic, or if the channel is in use. Raises BadArgumentError if the channel is invalid for the network. """ def _have_attachment(nic, query): """Return whether there are any attachments matching ``query`` for ``nic``. ``query`` should an argument suitable to pass to db.query(...).filter """ return model.NetworkAttachment.query.filter( model.NetworkAttachment.nic == nic, query, ).count() != 0 auth_backend = get_auth_backend() node = _must_find(model.Node, node) nic = _must_find_n(node, model.Nic, nic) network = _must_find(model.Network, network) if not node.project: raise errors.ProjectMismatchError("Node not in project") auth_backend.require_project_access(node.project) project = node.project allocator = get_network_allocator() if nic.port is None: raise errors.NotFoundError("No port is connected to given nic.") if nic.current_action: raise errors.BlockedError( "A networking operation is already active on the nic.") if (network.access) and (project not in network.access): raise errors.ProjectMismatchError( "Project does not have access to given network.") if _have_attachment(nic, model.NetworkAttachment.network == network): raise errors.BlockedError( "The network is already attached to the nic.") if channel is None: channel = allocator.get_default_channel() if _have_attachment(nic, model.NetworkAttachment.channel == channel): raise errors.BlockedError("The channel is already in use on the nic.") if not allocator.is_legal_channel_for(channel, network.network_id): raise errors.BadArgumentError( "Channel %r, is not legal for this network." % channel) db.session.add( model.NetworkingAction(type='modify_port', nic=nic, new_network=network, channel=channel)) db.session.commit() return '', 202