Ejemplo n.º 1
0
class User(db.Model):
    """A user of the HIL.

    A user can be a member of any number of projects, which grants them access
    to that process's resources. A user may also be flagged as an administrator
    """
    id = db.Column(BigIntegerType, primary_key=True)
    label = db.Column(db.String, nullable=False)

    is_admin = db.Column(db.Boolean, nullable=False, default=False)
    # The user's salted & hashed password. We currently use sha512 as the
    # hashing algorithm:
    hashed_password = db.Column(db.String)

    # The projects of which the user is a member.
    projects = db.relationship('Project',
                               secondary='user_projects',
                               backref='users')

    def __init__(self, label, password, is_admin=False):
        """Create a user `label` with the specified (plaintext) password."""
        self.label = label
        self.is_admin = is_admin
        self.set_password(password)

    def verify_password(self, password):
        """Return whether `password` is the user's (plaintext) password."""
        return sha512_crypt.verify(password, self.hashed_password)

    def set_password(self, password):
        """Set the user's password to `password` (which must be plaintext)."""
        self.hashed_password = sha512_crypt.encrypt(password)
Ejemplo n.º 2
0
class DellN3000(Switch):
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/' \
        'delln3000'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(db.Integer, db.ForeignKey('switch.id'), primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    dummy_vlan = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'username':
            basestring,
            'hostname':
            basestring,
            'password':
            basestring,
            'dummy_vlan':
            schema.And(schema.Use(int), lambda v: 0 < v and v <= 4093,
                       schema.Use(str)),
        }).validate(kwargs)

    def session(self):
        return _DellN3000Session.connect(self)
Ejemplo n.º 3
0
class Nexus(Switch):
    """A Cisco Nexus switch."""

    api_name = 'http://schema.massopencloud.org/haas/v0/switches/nexus'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    dummy_vlan = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'username':
            basestring,
            'hostname':
            basestring,
            'password':
            basestring,
            'dummy_vlan':
            schema.And(schema.Use(int), lambda v: 0 <= v and v <= 4096,
                       schema.Use(str)),
        }).validate(kwargs)

    def session(self):
        return _Session.connect(self)

    @staticmethod
    def validate_port_name(port):
        """
        Valid port names for this switch are of the form: Ethernet1/12,
        ethernet1/12, Ethernet1/0/10, or ethernet1/0/10
        """

        val = re.compile(r'^(E|e)thernet\d+/\d+(/\d+)?$')
        if not val.match(port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the form: Ethernet1/12"
                                   " ethernet1/12, Ethernet1/0/10, or"
                                   " ethernet1/0/10")
        return

    def get_capabilities(self):
        return ['nativeless-trunk-mode']
Ejemplo n.º 4
0
class DellN3000(Switch):
    """Dell N3000 switch."""

    api_name = 'http://schema.massopencloud.org/haas/v0/switches/' \
        'delln3000'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    dummy_vlan = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'username':
            basestring,
            'hostname':
            basestring,
            'password':
            basestring,
            'dummy_vlan':
            schema.And(schema.Use(int), lambda v: 0 < v and v <= 4093,
                       schema.Use(str)),
        }).validate(kwargs)

    def session(self):
        return _DellN3000Session.connect(self)

    @staticmethod
    def validate_port_name(port):
        """
        Valid port names for this switch are of the form gi1/0/11,
        te1/0/12, gi1/12, or te1/3
        """

        val = re.compile(r'^(gi|te)\d+/\d+(/\d+)?$')
        if not val.match(port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the form gi1/0/11, "
                                   "te1/0/12, gi1/12, or te1/3")
        return
Ejemplo n.º 5
0
class Vlan(db.Model):
    """A VLAN for the Dell switch

    This is used to track which vlan numbers are available; when a Network is
    created, it must allocate a Vlan, to ensure that:

    1. The VLAN number it is using is unique, and
    2. The VLAN number is actually allocated to the HIL; on some deployments
       we may have specific vlan numbers that we are allowed to use.
    """
    id = db.Column(BigIntegerType, primary_key=True)
    vlan_no = db.Column(db.Integer, nullable=False, unique=True)
    available = db.Column(db.Boolean, nullable=False)

    def __init__(self, vlan_no):
        self.vlan_no = vlan_no
        self.available = True
Ejemplo n.º 6
0
Archivo: dell.py Proyecto: mikelyy/hil
class PowerConnect55xx(Switch):
    """Dell powerconnect 5500 series switch."""

    api_name = 'http://schema.massopencloud.org/haas/v0/switches/' \
        'powerconnect55xx'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        Schema({
            'username': basestring,
            'hostname': basestring,
            'password': basestring,
        }).validate(kwargs)

    def session(self):
        return _PowerConnect55xxSession.connect(self)

    @staticmethod
    def validate_port_name(port):
        """
        Valid port names for this switch are of the form gi1/0/11,
        te1/0/12, gi1/12, or te1/3
        """

        val = re.compile(r'^(gi|te)\d+/\d+(/\d+)?$')
        if not val.match(port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the form gi1/0/11, "
                                   "te1/0/12, gi1/12, or te1/3")
        return

    def get_capabilities(self):
        return ['nativeless-trunk-mode']
Ejemplo n.º 7
0
class PowerConnect55xx(Switch):
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/' \
        'powerconnect55xx'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(db.Integer, db.ForeignKey('switch.id'), primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'username': basestring,
            'hostname': basestring,
            'password': basestring,
        }).validate(kwargs)

    def session(self):
        return _PowerConnect55xxSession.connect(self)
Ejemplo n.º 8
0
        self.is_admin = is_admin
        self.set_password(password)

    def verify_password(self, password):
        """Return whether `password` is the user's (plaintext) password."""
        return sha512_crypt.verify(password, self.hashed_password)

    def set_password(self, password):
        """Set the user's password to `password` (which must be plaintext)."""
        self.hashed_password = sha512_crypt.encrypt(password)


# A joining table for users and projects, which have a many to many
# relationship:
user_projects = db.Table('user_projects',
                         db.Column('user_id', db.ForeignKey('user.id')),
                         db.Column('project_id', db.ForeignKey('project.id')))


@rest_call('PUT',
           '/auth/basic/user/<user>',
           schema=Schema({
               'user': basestring,
               'password': basestring,
               Optional('is_admin'): bool,
           }),
           dont_log=('password', ))
def user_create(user, password, is_admin=False):
    """Create user with given password.

    If the user already exists, a DuplicateError will be raised.
Ejemplo n.º 9
0
class DellNOS9(Switch, SwitchSession):
    """Dell S3048-ON running Dell NOS9"""
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/dellnos9'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'), primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    interface_type = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'hostname': basestring,
            'username': basestring,
            'password': basestring,
            'interface_type': basestring,
        }).validate(kwargs)

    def session(self):
        return self

    def ensure_legal_operation(self, nic, op_type, channel):
        # get the network attachments for <nic> from the database
        table = model.NetworkAttachment
        query = db.session.query(table).filter(table.nic_id == nic.id)

        if channel != 'vlan/native' and op_type == 'connect' and \
           query.filter(table.channel == 'vlan/native').count() == 0:
            # checks if it is trying to attach a trunked network, and then in
            # in the db see if nic does not have any networks attached natively
            raise BlockedError("Please attach a native network first")
        elif channel == 'vlan/native' and op_type == 'detach' and \
                query.filter(table.channel != 'vlan/native').count() > 0:
            # if it is detaching a network, then check in the database if there
            # are any trunked vlans.
            raise BlockedError("Please remove all trunked Vlans"
                               " before removing the native vlan")
        else:
            return

    @staticmethod
    def validate_port_name(port):
        """Valid port names for this switch are of the form 1/0/1 or 1/2"""

        if not re.match(r'^\d+/\d+(/\d+)?$', port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the form 1/0/1 or 1/2")
        return

    def disconnect(self):
        """Since the switch is not connection oriented, we don't need to
        establish a session or disconnect from it."""

    def modify_port(self, port, channel, new_network):
        (port,) = filter(lambda p: p.label == port, self.ports)
        interface = port.label

        if channel == 'vlan/native':
            if new_network is None:
                self._remove_native_vlan(interface)
                self._port_shutdown(interface)
            else:
                self._set_native_vlan(interface, new_network)
        else:
            vlan_id = channel.replace('vlan/', '')
            legal = get_network_allocator(). \
                is_legal_channel_for(channel, vlan_id)
            assert legal, "HIL passed an invalid channel to the switch!"

            if new_network is None:
                self._remove_vlan_from_trunk(interface, vlan_id)
            else:
                assert new_network == vlan_id
                self._add_vlan_to_trunk(interface, vlan_id)

    def revert_port(self, port):
        self._remove_all_vlans_from_trunk(port)
        if self._get_native_vlan(port) is not None:
            self._remove_native_vlan(port)
        self._port_shutdown(port)

    def get_port_networks(self, ports):
        response = {}
        for port in ports:
            response[port] = self._get_vlans(port.label)
            native = self._get_native_vlan(port.label)
            if native is not None:
                response[port].append(native)

        return response

    def _get_vlans(self, interface):
        """ Return the vlans of a trunk port.

        Does not include the native vlan. Use _get_native_vlan.

        Args:
            interface: interface to return the vlans of

        Returns: List containing the vlans of the form:
        [('vlan/vlan1', vlan1), ('vlan/vlan2', vlan2)]
        """

        # It uses the REST API CLI which is slow but it is the only way
        # because the switch is VLAN centric. Doing a GET on interface won't
        # return the VLANs on it, we would have to do get on all vlans (if that
        # worked reliably in the first place) and then find our interface there
        # which is not feasible.

        if not self._is_port_on(interface):
            return []
        response = self._get_port_info(interface)

        # finds a comma separated list of integers and/or ranges starting with
        # T. Sample T12,14-18,23,28,80-90 or T20 or T20,22 or T20-22
        match = re.search(r'T(\d+(-\d+)?)(,\d+(-\d+)?)*', response)
        if match is None:
            return []
        range_str = match.group().replace('T', '').split(',')
        vlan_list = []
        # need to interpret the ranges to numbers. e.g. 14-18 as 14,15,16,17,18
        for num_str in range_str:
            if '-' in num_str:
                num_str = num_str.split('-')
                for x in range(int(num_str[0]), int(num_str[1])+1):
                    vlan_list.append(str(x))
            else:
                vlan_list.append(num_str)

        return [('vlan/%s' % x, x) for x in vlan_list]

    def _get_native_vlan(self, interface):
        """ Return the native vlan of an interface.

        Args:
            interface: interface to return the native vlan of

        Returns: Tuple of the form ('vlan/native', vlan) or None

        Similar to _get_vlans()
        """
        if not self._is_port_on(interface):
            return None
        response = self._get_port_info(interface)
        match = re.search(r'NativeVlanId:(\d+)\.', response)
        if match is not None:
            vlan = match.group(1)
        else:
            logger.error('Unexpected: No native vlan found')
            return

        # FIXME: At this point, we are unable to remove the default native vlan
        # The switchport defaults to native vlan 1. Our deployment tests make
        # assertions that a switchport has no default vlan at start.
        # To get the tests to pass, we return None if we see the switchport has
        # a default native vlan (=1). This needs to be fixed ASAP.
        if vlan == '1':
            return None

        return ('vlan/native', vlan)

    def _get_port_info(self, interface):
        """Returns the output of a show interface command. This removes all
        spaces from the response before returning it which is then parsed by
        the caller.

        Sample Response:
        u"<outputxmlns='http://www.dell.com/ns/dell:0.1/root'>\n
        <command>show interfaces switchport GigabitEthernet1/3\r\n\r\n
        Codes: U-Untagged T-Tagged\r\n x-Dot1x untagged,X-Dot1xtagged\r\n
        G-GVRP tagged,M-Trunk\r\n i-Internal untagged, I-Internaltagged,
        v-VLTuntagged, V-VLTtagged\r\n\r\n Name:GigabitEthernet1/3\r\n 802.1Q
        Tagged:Hybrid\r\n Vlan membership:\r\n Q Vlans\r\n U 1512 \r\n T 1511
        1612-1614,1700\r\n\r\n Native Vlan Id: 1512.\r\n\r\n\r\n\r\n
        MOC-Dell-S3048-ON#</command>\n</output>\n"
        """
        command = 'interfaces switchport %s %s' % \
            (self.interface_type, interface)
        response = self._execute(interface, SHOW, command)
        return response.text.replace(' ', '')

    def _add_vlan_to_trunk(self, interface, vlan):
        """ Add a vlan to a trunk port.

        If the port is not trunked, its mode will be set to trunk.

        Args:
            interface: interface to add the vlan to
            vlan: vlan to add
        """
        if not self._is_port_on(interface):
            self._port_on(interface)
        command = 'interface vlan ' + vlan + '\r\n tagged ' + \
            self.interface_type + ' ' + interface
        self._execute(interface, CONFIG, command)

    def _remove_vlan_from_trunk(self, interface, vlan):
        """ Remove a vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
            vlan: vlan to remove
        """
        command = self._remove_vlan_command(interface, vlan)
        self._execute(interface, CONFIG, command)

    def _remove_all_vlans_from_trunk(self, interface):
        """ Remove all vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
        """
        command = ''
        for vlan in self._get_vlans(interface):
            command += self._remove_vlan_command(interface, vlan[1]) + '\r\n '
        # execute command only if there are some vlans to remove, otherwise
        # the switch complains
        if command is not '':
            self._execute(interface, CONFIG, command)

    def _remove_vlan_command(self, interface, vlan):
        """Returns command to remove <vlan> from <interface>"""
        return 'interface vlan ' + vlan + '\r\n no tagged ' + \
            self.interface_type + ' ' + interface

    def _set_native_vlan(self, interface, vlan):
        """ Set the native vlan of an interface.

        Args:
            interface: interface to set the native vlan to
            vlan: vlan to set as the native vlan

        Method relies on the REST API CLI which is slow
        """
        if not self._is_port_on(interface):
            self._port_on(interface)
        command = 'interface vlan ' + vlan + '\r\n untagged ' + \
            self.interface_type + ' ' + interface
        self._execute(interface, CONFIG, command)

    def _remove_native_vlan(self, interface):
        """ Remove the native vlan from an interface.

        Args:
            interface: interface to remove the native vlan from.vlan
        """
        try:
            vlan = self._get_native_vlan(interface)[1]
            command = 'interface vlan ' + vlan + '\r\n no untagged ' + \
                self.interface_type + ' ' + interface
            self._execute(interface, CONFIG, command)
        except TypeError:
            logger.error('No native vlan to remove')

    def _port_shutdown(self, interface):
        """ Shuts down <interface>

        Turn off portmode hybrid, disable switchport, and then shut down the
        port. All non-default vlans must be removed before calling this.
        """

        url = self._construct_url(interface=interface)
        interface = self._convert_interface_type(self.interface_type) + \
            interface.replace('/', '-')
        payload = '<interface><name>%s</name><portmode><hybrid>false' \
                  '</hybrid></portmode><shutdown>true</shutdown>' \
                  '</interface>' % interface

        self._make_request('PUT', url, data=payload)

    def _port_on(self, interface):
        """ Turns on <interface>

        Turn on port and enable hybrid portmode and switchport.
        """

        url = self._construct_url(interface=interface)
        interface = self._convert_interface_type(self.interface_type) + \
            interface.replace('/', '-')
        payload = '<interface><name>%s</name><portmode><hybrid>true' \
                  '</hybrid></portmode><switchport></switchport>' \
                  '<shutdown>false</shutdown></interface>' % interface

        self._make_request('PUT', url, data=payload)

    def _is_port_on(self, port):
        """ Returns a boolean that tells the status of a switchport"""

        # the url here requires a suffix to GET the shutdown tag in response.
        url = self._construct_url(interface=port) + '\?with-defaults'
        response = self._make_request('GET', url)
        root = etree.fromstring(response.text)
        shutdown = root.find(self._construct_tag('shutdown')).text

        assert shutdown in ('false', 'true'), "unexpected state of switchport"
        return shutdown == 'false'

    # HELPER METHODS *********************************************

    def _execute(self, interface, command_type, command):
        """This method gets the url & the payload and executes <command>"""
        url = self._construct_url()
        payload = self._make_payload(command_type, command)
        return self._make_request('POST', url, data=payload)

    def _construct_url(self, interface=None):
        """ Construct the API url for a specific interface.

        Args:
            interface: interface to construct the url for

        Returns: string with the url for a specific interface and operation

        If interface is None, then it returns the URL for REST API CLI.
        """

        if interface is None:
            return '%s/api/running/dell/_operations/cli' % self.hostname

        try:
            self.validate_port_name(interface)
            # if `interface` refers to port name
            # the urls have dashes instead of slashes in interface names
            interface = interface.replace('/', '-')
            interface_type = self._convert_interface_type(self.interface_type)
        except BadArgumentError:
            # interface refers to `vlan`
            interface_type = 'vlan-'

        return ''.join([self.hostname, '/api/running/dell/interfaces/'
                       'interface/', interface_type, interface])

    @staticmethod
    def _convert_interface_type(interface_type):
        """ Convert the interface type from switch CLI form to what the API
        server understands.

        Args:
            interface: the interface in the CLI-form

        Returns: string interface
        """
        iftypes = {'GigabitEthernet': 'gige-',
                   'TenGigabitEthernet': 'tengig-',
                   'TwentyfiveGigabitEthernet': 'twentyfivegig-',
                   'fortyGigE': 'fortygig-',
                   'peGigabitEthernet': 'pegig-',
                   'FiftyGigabitEthernet': 'fiftygig-',
                   'HundredGigabitEthernet': 'hundredgig-'}

        return iftypes[interface_type]

    @property
    def _auth(self):
        return self.username, self.password

    @staticmethod
    def _make_payload(command, command_type):
        """Makes payload for passing CLI commands using the REST API"""

        return '<input><%s>%s</%s></input>' % (command, command_type, command)

    @staticmethod
    def _construct_tag(name):
        """ Construct the xml tag by prepending the dell tag prefix. """
        return '{http://www.dell.com/ns/dell:0.1/root}%s' % name

    def _make_request(self, method, url, data=None):
        r = requests.request(method, url, data=data, auth=self._auth)
        if r.status_code >= 400:
            logger.error('Bad Request to switch. Response: %s', r.text)
        return r
Ejemplo n.º 10
0
    class DeferredTestSwitch_(Switch):
        '''DeferredTestSwitch

        This is a switch implemented to test the deferred.apply_networking()
        function.  It is needed for a custom implementation of the switch's
        apply_networking() that counts the networking actions as
        apply_networking() is called to ensure it is incremental.

        It is defined as a fixture because if it is defined as a class with
        global scope it's going to be defined for every test; so this will be
        picked up by migration tests and those would fail because there will be
        no migration scripts for this switch's table.
        '''

        api_name = 'http://schema.massopencloud.org/haas/v0/switches/deferred'

        __mapper_args__ = {
            'polymorphic_identity': api_name,
        }

        id = db.Column(db.Integer,
                       db.ForeignKey('switch.id'),
                       primary_key=True)
        hostname = db.Column(db.String, nullable=False)
        username = db.Column(db.String, nullable=False)
        password = db.Column(db.String, nullable=False)
        last_count = None

        @staticmethod
        def validate(kwargs):
            """Implement Switch.validate.

            This currently doesn't check anything; in theory we should validate
            that the arguments are sane, but right now aren't worrying about it
            since this method is only called from the API server, and we never
            use this switch type there.
            """

        def session(self):
            """Return a switch session.

            This just returns self, since there's no connection to speak of.
            """
            return self

        def disconnect(self):
            """Implement the session's disconnect() method.

            This is a no-op, since session() doesn't establish a connection.
            """

        def modify_port(self, port, channel, network_id):
            """Implement Switch.modify_port.

            This implementation keeps track of how many pending
            NetworkingActions there are, and fails the test if apply_networking
            calls it without committing the previous change.
            """
            # get a new connection to database so that this method does
            # not see uncommited changes by `apply_networking`
            local_db = new_db()
            current_count = local_db.session \
                .query(model.NetworkingAction).filter_by(status='PENDING') \
                .count()

            local_db.session.commit()
            local_db.session.close()

            if self.last_count is None:
                self.last_count = current_count
            else:
                assert current_count == self.last_count - 1, \
                  "network daemon did not commit previous change!"
                self.last_count = current_count

        def revert_port(self, port):
            """Implement Switch.revert_port.

            This always fails, which the tests rely on to check
            error handling behavior.
            """
            raise RevertPortError('revert_port always fails.')
Ejemplo n.º 11
0
Archivo: brocade.py Proyecto: vsemp/hil
class Brocade(Switch, SwitchSession):
    """Brocade switch"""

    api_name = 'http://schema.massopencloud.org/haas/v0/switches/brocade'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    interface_type = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'hostname': basestring,
            'username': basestring,
            'password': basestring,
            'interface_type': basestring,
        }).validate(kwargs)

    def session(self):
        return self

    @staticmethod
    def validate_port_name(port):
        """Valid port names for this switch are of the form 1/0/1 or 1/2"""

        val = re.compile(r'^\d+/\d+(/\d+)?$')
        if not val.match(port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the from 1/0/1 or 1/2")
        return

    def disconnect(self):
        pass

    def modify_port(self, port, channel, new_network):
        # XXX: We ought to be able to do a Port.query ... one() here, but
        # there's somthing I(zenhack)  don't understand going on with when
        # things are committed in the tests for this driver, and we don't
        # get any results that way. We should figure out what's going on with
        # that test and change this.
        (port, ) = filter(lambda p: p.label == port, self.ports)
        interface = port.label

        if channel == 'vlan/native':
            if new_network is None:
                self._remove_native_vlan(interface)
            else:
                self._set_native_vlan(interface, new_network)
        else:
            match = re.match(re.compile(r'vlan/(\d+)'), channel)
            assert match is not None, "HIL passed an invalid channel to the" \
                " switch!"
            vlan_id = match.groups()[0]

            if new_network is None:
                self._remove_vlan_from_trunk(interface, vlan_id)
            else:
                assert new_network == vlan_id
                self._add_vlan_to_trunk(interface, vlan_id)

    def revert_port(self, port):
        self._remove_all_vlans_from_trunk(port)
        if self._get_native_vlan(port) is not None:
            self._remove_native_vlan(port)

    def get_port_networks(self, ports):
        """Get port configurations of the switch.

        Args:
            ports: List of ports to get the configuration for.

        Returns: Dictionary containing the configuration of the form:
        {
            Port<"port-3">: [("vlan/native", "23"), ("vlan/52", "52")],
            Port<"port-7">: [("vlan/23", "23")],
            Port<"port-8">: [("vlan/native", "52")],
            ...
        }

        """
        response = {}
        for port in ports:
            response[port] = filter(None,
                                    [self._get_native_vlan(port.label)]) \
                                    + self._get_vlans(port.label)
        return response

    def _get_mode(self, interface):
        """ Return the mode of an interface.

        Args:
            interface: interface to return the mode of

        Returns: 'access' or 'trunk'

        Raises: AssertionError if mode is invalid.

        """
        url = self._construct_url(interface, suffix='mode')
        response = self._make_request('GET', url)
        root = etree.fromstring(response.text)
        mode = root.find(self._construct_tag('vlan-mode')).text
        return mode

    def _enable_and_set_mode(self, interface, mode):
        """ Enables switching and sets the mode of an interface.

        Args:
            interface: interface to set the mode of
            mode: 'access' or 'trunk'

        Raises: AssertionError if mode is invalid.

        """
        # Enable switching
        url = self._construct_url(interface)
        payload = '<switchport></switchport>'
        self._make_request('POST',
                           url,
                           data=payload,
                           acceptable_error_codes=(409, ))

        # Set the interface mode
        if mode in ['access', 'trunk']:
            url = self._construct_url(interface, suffix='mode')
            payload = '<mode><vlan-mode>%s</vlan-mode></mode>' % mode
            self._make_request('PUT', url, data=payload)
        else:
            raise AssertionError('Invalid mode')

    def _get_vlans(self, interface):
        """ Return the vlans of a trunk port.

        Does not include the native vlan. Use _get_native_vlan.

        Args:
            interface: interface to return the vlans of

        Returns: List containing the vlans of the form:
        [('vlan/vlan1', vlan1), ('vlan/vlan2', vlan2)]
        """
        try:
            url = self._construct_url(interface, suffix='trunk')
            response = self._make_request('GET', url)
            root = etree.fromstring(response.text)
            vlans = root.\
                find(self._construct_tag('allowed')).\
                find(self._construct_tag('vlan')).\
                find(self._construct_tag('add')).text
            return [('vlan/%s' % x, x) for x in vlans.split(',')]
        except AttributeError:
            return []

    def _get_native_vlan(self, interface):
        """ Return the native vlan of an interface.

        Args:
            interface: interface to return the native vlan of

        Returns: Tuple of the form ('vlan/native', vlan) or None
        """
        try:
            url = self._construct_url(interface, suffix='trunk')
            response = self._make_request('GET', url)
            root = etree.fromstring(response.text)
            vlan = root.find(self._construct_tag('native-vlan')).text
            return ('vlan/native', vlan)
        except AttributeError:
            return None

    def _add_vlan_to_trunk(self, interface, vlan):
        """ Add a vlan to a trunk port.

        If the port is not trunked, its mode will be set to trunk.

        Args:
            interface: interface to add the vlan to
            vlan: vlan to add
        """
        self._enable_and_set_mode(interface, 'trunk')
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><add>%s</vlan></vlan>' % vlan
        self._make_request('PUT', url, data=payload)

    def _remove_vlan_from_trunk(self, interface, vlan):
        """ Remove a vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
            vlan: vlan to remove
        """
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><remove>%s</remove></vlan>' % vlan
        self._make_request('PUT', url, data=payload)

    def _remove_all_vlans_from_trunk(self, interface):
        """ Remove all vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
        """
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><none>true</none></vlan>'
        requests.put(url, data=payload, auth=self._auth)

    def _set_native_vlan(self, interface, vlan):
        """ Set the native vlan of an interface.

        Args:
            interface: interface to set the native vlan to
            vlan: vlan to set as the native vlan
        """
        self._enable_and_set_mode(interface, 'trunk')
        self._disable_native_tag(interface)
        url = self._construct_url(interface, suffix='trunk')
        payload = '<trunk><native-vlan>%s</native-vlan></trunk>' % vlan
        self._make_request('PUT', url, data=payload)

    def _remove_native_vlan(self, interface):
        """ Remove the native vlan from an interface.

        Args:
            interface: interface to remove the native vlan from
        """
        url = self._construct_url(interface, suffix='trunk/native-vlan')
        self._make_request('DELETE', url)

    def _disable_native_tag(self, interface):
        """ Disable tagging of the native vlan

        Args:
            interface: interface to disable the native vlan tagging of

        """
        url = self._construct_url(interface, suffix='trunk/tag/native-vlan')
        self._make_request('DELETE', url, acceptable_error_codes=(404, ))

    def _construct_url(self, interface, suffix=''):
        """ Construct the API url for a specific interface appending suffix.

        Args:
            interface: interface to construct the url for
            suffix: suffix to append at the end of the url

        Returns: string with the url for a specific interface and operation
        """
        # %22 is the encoding for double quotes (") in urls.
        # % escapes the % character.
        # Double quotes are necessary in the url because switch ports contain
        # forward slashes (/), ex. 101/0/10 is encoded as "101/0/10".
        return '%(hostname)s/rest/config/running/interface/' \
            '%(interface_type)s/%%22%(interface)s%%22%(suffix)s' \
            % {
                  'hostname': self.hostname,
                  'interface_type': self.interface_type,
                  'interface': interface,
                  'suffix': '/switchport/%s' % suffix if suffix else ''
            }

    @property
    def _auth(self):
        return self.username, self.password

    @staticmethod
    def _construct_tag(name):
        """ Construct the xml tag by prepending the brocade tag prefix. """
        return '{urn:brocade.com:mgmt:brocade-interface}%s' % name

    def _make_request(self, method, url, data=None, acceptable_error_codes=()):
        r = requests.request(method, url, data=data, auth=self._auth)
        if r.status_code >= 400 and \
           r.status_code not in acceptable_error_codes:
            logger.error('Bad Request to switch. Response: %s', r.text)
        return r
Ejemplo n.º 12
0
class Ovs(Switch, SwitchSession):
    """ Driver for openvswitch. """
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/ovs'
    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        """Checks input to match switch parameters."""
        schema.Schema({
            'username': basestring,
            'hostname': basestring,
            'password': basestring,
        }).validate(kwargs)

# 1. Public Methods:

    def get_capabilities(self):
        return ['nativeless-trunk-mode']

    def ovs_connect(self, *command_strings):
        """Interacts with the Openvswitch.

        Args:
            *command_strings (tuple) : tuple of shell commands required
                        to make changes to openvswitch
        Raises: SwitchError
        Returns: If successful returns None else logs error message
        """
        try:
            for command in command_strings:
                arg_list = shlex.split(command)
                subprocess.check_call(arg_list)
        except subprocess.CalledProcessError as e:
            logger.error('%s', e)
            raise SwitchError('ovs command failed: ')

    def get_port_networks(self, ports):

        response = {}
        for port in ports:
            trunk_id_list = self._interface_info(port.label)['trunks']
            if trunk_id_list == []:
                response[port] = []
            else:
                result = []
                for trunk in trunk_id_list:
                    name = "vlan/" + trunk
                    result.append((name, trunk))
                response[port] = result
            native = self._interface_info(port.label)['tag']
            if native != []:
                response[port].append(("vlan/native", native))

        return response

#        (port,) = filter(lambda p: p.label == port, self.ports)
#        interface = port.label
#        port_info = self._interface_info(interface)
#        networks = []
#        if port_info['trunks']:
#            for vlan in port_info['trunks']:
#                networks.append(vlan)
#        if port_info['tag']:
#            networks.append(port_info['tag'][0])
#        return networks

    def revert_port(self, port):

        shell_cmd_1 = 'sudo ovs-vsctl del-port {port}'.format(port=port)
        shell_cmd_2 = 'sudo ovs-vsctl add-port {switch} {port}'.format(
            switch=self.hostname, port=port) + ' vlan_mode=native-untagged'
        self.ovs_connect(shell_cmd_1, shell_cmd_2)

    def modify_port(self, port, channel, new_network):

        (port, ) = filter(lambda p: p.label == port, self.ports)
        interface = port.label

        if channel == 'vlan/native':
            if new_network is None:
                return self._remove_native_vlan(interface)
            else:
                return self._set_native_vlan(interface, new_network)
        else:
            match = re.match(re.compile(r'vlan/(\d+)'), channel)
            assert match is not None, "HIL passed an invalid channel to the" \
                " switch!"
            vlan_id = match.groups()[0]

            if new_network is None:
                return self._remove_vlan_from_port(interface, vlan_id)
            else:
                assert new_network == vlan_id
                return self._add_vlan_to_trunk(interface, vlan_id)

# 2. Private Methods:

    def _string_to_list(self, a_string):
        """Converts a string representation of list to list.
        Args:
            a_string: list output recieved as string.
                e.g. Strings starting with '[' and ending with ']'
        Returns: object of list type.
                 Empty list is put as None.
        """
        if a_string == '[]':
            return ast.literal_eval(a_string)
        else:
            a_string = a_string.replace("[", "['").replace("]", "']")
            a_string = a_string.replace(",", "','")
            a_list = ast.literal_eval(a_string)
            a_list = [ele.strip() for ele in a_list]
            return a_list

    def _string_to_dict(self, a_string):
        """Converts a string representation of dictionary
        into a dictionary type object.

        Args:
            a_string: dictionary recieved as type string
                eg. Strings starting with '{' and ending with '}'
        Returns: Object of dictionary type.
        """
        if a_string == '{}':
            a_dict = ast.literal_eval(a_string)
            return a_dict
        else:
            a_string = a_string.replace("{", "{'").replace("}", "'}")
            a_string = a_string.replace(":", "':'").replace(",", "','")
            a_dict = ast.literal_eval(a_string)
            a_dict = {k.strip(): v.strip() for k, v in a_dict.iteritems()}
            return a_dict

    def _interface_info(self, port):
        """Gets latest configuration of port from switch.

        Args:
            port: Valid port name
        Returns: configuration status of the port
        """
        # This function is differnet then `ovs_connect` as it uses
        # subprocess.check_output because it only needs read info from switch
        # and pass the output to calling funtion.
        shell_cmd = "sudo ovs-vsctl list port {port}".format(port=port)
        args = shlex.split(shell_cmd)
        try:
            output = subprocess.check_output(args)
        except subprocess.CalledProcessError as e:
            logger.error(" %s ", e)
            raise SwitchError('Ovs command failed: ')
        output = output.split('\n')
        output.remove('')
        i_info = dict(s.split(':') for s in output)
        i_info = {k.strip(): v.strip() for k, v in i_info.iteritems()}
        # above statement removes extra white spaces from the keys and values.
        # That is important since other calls will be querying this dictionary.
        for x in i_info.keys():
            if i_info[x][0] == "{":
                i_info[x] = self._string_to_dict(i_info[x])
            elif i_info[x][0] == "[":
                i_info[x] = self._string_to_list(i_info[x])
        return i_info

    def _remove_native_vlan(self, port):
        """Removes native vlan from a trunked port.
        If it is the last vlan to be removed, it disables the port and
        reverts its state to default configuration
        Args:
            port: Valid switch port
        Returns: if successful None else error message
        """
        port_info = self._interface_info(port)
        vlan_id = port_info['tag']
        shell_cmd = "sudo ovs-vsctl remove port {port} tag {vlan_id}".format(
            port=port, vlan_id=vlan_id)
        return self.ovs_connect(shell_cmd)

    def _set_native_vlan(self, port, new_network):
        """Sets native vlan for a trunked port.
        It enables the port, if it is the first vlan for the port.
        Args:
            port: valid port of switch
            new_network: vlan_id
        """
        shell_cmd = ("sudo ovs-vsctl set port {port} tag={netid}".format(
            port=port, netid=new_network) + " vlan_mode=native-untagged")

        return self.ovs_connect(shell_cmd)

    def _add_vlan_to_trunk(self, port, vlan_id):
        """ Adds vlans to a trunk port. """
        #import pdb; pdb.set_trace()
        port_info = self._interface_info(port)
        netLen = len(port_info['trunks'])
        if len > 0:
            shell_cmd = "sudo ovs-vsctl set port {port} trunks={vlans}".format(
                port=port, vlans=vlan_id)
        else:
            all_trunks = ','.join(port_info['trunks']) + ',' + vlan_id
            shell_cmd = "sudo ovs-vsctl set port {port} trunks={vlans}".format(
                port=port, vlans=all_trunks)

        return self.ovs_connect(shell_cmd)

    def _remove_vlan_from_port(self, port, vlan_id):
        """ removes a single vlan specified by `vlan_id` """
        port_info = self._interface_info(port)
        shell_cmd = "sudo ovs-vsctl remove port {port} trunks {vlan}".format(
            port=port, vlan=vlan_id)
        if port_info['trunks']:
            return self.ovs_connect(shell_cmd)
        return None

# 3. superclass methods (Not Required):

    @staticmethod
    def validate_port_name(port):
        """This driver accepts any string as a port name. """

    def session(self):
        return self

    def disconnect(self):
        return self
Ejemplo n.º 13
0
class Ovs(Switch, SwitchSession):
    """ Driver for openvswitch. """
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/ovs'
    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(
            BigIntegerType, db.ForeignKey('switch.id'), primary_key=True
            )
    ovs_bridge = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        """Checks input to match switch parameters."""
        schema.Schema({
            'ovs_bridge': basestring,
        }).validate(kwargs)

# 1. Public Methods:

    def get_capabilities(self):
        return ['nativeless-trunk-mode']

    def ovs_connect(self, *command_strings):
        """Interacts with the Openvswitch.

        Args:
            *command_strings (tuple) : tuple of list of arguments required
                        to make changes to openvswitch
        Raises: SwitchError
        Returns: If successful returns None else logs error message
        """
        try:
            for arg_list in command_strings:
                subprocess.check_call(arg_list)
        except subprocess.CalledProcessError as e:
            logger.error('%s', e)
            raise SwitchError('ovs command failed: %s', e)

    def get_port_networks(self, ports):

        response = {}
        for port in ports:
            trunk_id_list = self._interface_info(port.label)['trunks']
            if trunk_id_list == []:
                response[port] = []
            else:
                result = []
                for trunk in trunk_id_list:
                    name = "vlan/"+trunk
                    result.append((name, trunk))
                response[port] = result
            native = self._interface_info(port.label)['tag']
            if native != []:
                response[port].append(("vlan/native", native))

        return response

    def revert_port(self, port):

        args_1 = ['sudo', 'ovs-vsctl', 'del-port', str(port)]
        args_2 = [
                'sudo', 'ovs-vsctl', 'add-port', str(self.ovs_bridge),
                str(port), 'vlan_mode=native-untagged'
                ]
        self.ovs_connect(args_1, args_2)

    def modify_port(self, port, channel, new_network):

        port_obj = Port.query.filter_by(label=port).one()
        interface = port_obj.label

        if channel == 'vlan/native':
            if new_network is None:
                return self._remove_native_vlan(interface)
            else:
                return self._set_native_vlan(interface, new_network)
        else:
            match = re.match(re.compile(r'vlan/(\d+)'), channel)
            assert match is not None, "HIL passed an invalid channel to the" \
                " switch!"
            vlan_id = match.groups()[0]

            if new_network is None:
                return self._remove_vlan_from_port(interface, vlan_id)
            else:
                assert new_network == vlan_id
                return self._add_vlan_to_trunk(interface, vlan_id)

    def _interface_info(self, port):
        """Gets latest configuration of port from switch.

        Args:
            port: Valid port name
        Returns intermediate output which processed to return final output:
        It is a string of the following format:
        '_uuid               : ad489368-9b53-4a3e-8732-697ad5141de9\n
        bond_downdelay      : 0\nbon    d_fake_iface     : false\n
        external_ids        : {}\nfake_bridge         : false\n
        interfaces              : [fc61c8ff99c5,fc61c8ff99c6]\n
        name                : "veth-0"\n
        statistics          : {abc:123    , def:xyz,  space  :  lot of it , \
                2345:some number}\n
        status              : {}\ntag                     : 100\n
        trunks              : [200,300,400]\n
        vlan_mode           : native-untagged\n'

        Which is used as input for further processing.
        Returns: A dictionary of configuration status of the port.
             String, Dictionary and list are valid values of this dictionary.
             eg: Sample output.
               {
                '_uuid': 'ad489368-9b53-4a3e-8732-697ad5141de9',
                'bond_downdelay': '0',
                'bond_fake_iface': 'false',
                'external_ids': {},
                'fake_bridge': 'false',
                'interfaces': ['fc61c8ff99c5', 'fc61c8ff99c6'],
                'name': '"veth-0"',
                'statistics': {'2345': 'some number','abc': '123','def': 'xyz'
                                ,'space': 'lot of it'},
                'status': {},
                'tag': '100',
                'trunks': ['200', '300', '400'],
                'vlan_mode': 'native-untagged'
              }
        """
        # This function is differnet then `ovs_connect` as it uses
        # subprocess.check_output because it only needs read info from switch
        # and pass the output to calling funtion.
        args = ['sudo', 'ovs-vsctl', 'list', 'port', str(port)]
        try:
            output = subprocess.check_output(args)
        except subprocess.CalledProcessError as e:
            logger.error(" %s ", e)
            raise SwitchError('Ovs command failed: %s', e)
        output = output.split('\n')
        output.remove('')
        i_info = dict(s.split(':', 1) for s in output)
        i_info = {k.strip(): v.strip() for k, v in i_info.iteritems()}
        for x in i_info.keys():
            if i_info[x][0] == "{":
                i_info[x] = string_to_dict(i_info[x])
            elif i_info[x][0] == "[":
                i_info[x] = string_to_list(i_info[x])
        return i_info

    def _remove_native_vlan(self, port):
        """Removes native vlan from a trunked port.
        If it is the last vlan to be removed, it disables the port and
        reverts its state to default configuration
        Args:
            port: Valid switch port
        Returns: if successful None else error message
        """
        port_info = self._interface_info(port)
        vlan_id = port_info['tag']
        args = [
                'sudo', 'ovs-vsctl', 'remove', 'port', str(port), 'tag',
                str(vlan_id)
                ]
        return self.ovs_connect(args)

    def _set_native_vlan(self, port, new_network):
        """Sets native vlan for a trunked port.
        It enables the port, if it is the first vlan for the port.
        Args:
            port: valid port of switch
            new_network: vlan_id
        """
        args = [
                'sudo', 'ovs-vsctl', 'set', 'port', str(port),
                'tag='+str(new_network), 'vlan_mode=native-untagged'
                ]

        return self.ovs_connect(args)

    def _add_vlan_to_trunk(self, port, vlan_id):
        """ Adds vlans to a trunk port. """
        port_info = self._interface_info(port)

        if not port_info['trunks']:
            args = [
                    'sudo', 'ovs-vsctl', 'set', 'port', str(port),
                    'trunks='+str(vlan_id)
                    ]
        else:
            all_trunks = ','.join(port_info['trunks'])+','+vlan_id
            args = [
                    'sudo', 'ovs-vsctl', 'set', 'port', str(port),
                    'trunks='+str(all_trunks)
                    ]

        return self.ovs_connect(args)

    def _remove_vlan_from_port(self, port, vlan_id):
        """ removes a single vlan specified by `vlan_id` """
        port_info = self._interface_info(port)
        args = [
                'sudo', 'ovs-vsctl', 'remove', 'port', str(port), 'trunks',
                str(vlan_id)
                ]
        if port_info['trunks']:
            return self.ovs_connect(args)
        return None

# 3. Other superclass methods:

    @staticmethod
    def validate_port_name(port):
        """This driver accepts any string as a port name. """

    def session(self):
        return self

    def disconnect(self):
        pass
Ejemplo n.º 14
0
    # assume this has something to do with search order, but this hangs
    # otherwise.
    paths_scratch = paths.copy()
    core_path = paths_scratch.pop('hil')
    configval = ' '.join([core_path] + list(paths_scratch.values()))

    config.set_main_option('version_locations', configval)
    return config


# Alembic will create this table itself if need be when doing "stamp" in the
# create_db  function below, but unless we declare it, db.drop_all() won't
# know about it, and will leave us with a one-table database.
AlembicVersion = db.Table(
    'alembic_version', db.metadata,
    db.Column('version_num', db.String(32), nullable=False))


def _expected_heads():
    cfg_path = join(dirname(__file__), 'migrations', 'alembic.ini')
    cfg = Config(cfg_path)
    _configure_alembic(cfg)
    cfg.set_main_option('script_location', dirname(cfg_path))
    script_dir = ScriptDirectory.from_config(cfg)
    return set(script_dir.get_heads())


def create_db():
    """Create and populate the initial database.

    The database connection must have been previously initialzed via
Ejemplo n.º 15
0
Archivo: ipmi.py Proyecto: djfinn14/hil
class Ipmi(Obm):
    valid_bootdevices = ['disk', 'pxe', 'none']

    id = db.Column(BigIntegerType, db.ForeignKey('obm.id'), primary_key=True)
    host = db.Column(db.String, nullable=False)
    user = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)

    api_name = 'http://schema.massopencloud.org/haas/v0/obm/ipmi'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    @staticmethod
    def validate(kwargs):
        schema.Schema({
            'type': Ipmi.api_name,
            'host': basestring,
            'user': basestring,
            'password': basestring,
        }).validate(kwargs)

    def _ipmitool(self, args):
        """Invoke ipmitool with the right host/pass etc. for this node.

        `args`- A list of any additional arguments to pass to ipmitool.
        Returns the exit status of ipmitool.

        Note: Includes the ``-I lanplus`` flag, available only in IPMI v2+.
        This is needed for machines which do not accept the older version.
        """
        status = call([
            'ipmitool',
            '-I',
            'lanplus',  # see docstring above
            '-U',
            self.user,
            '-P',
            self.password,
            '-H',
            self.host
        ] + args)

        if status != 0:
            logger = logging.getLogger(__name__)
            logger.info('Nonzero exit status form ipmitool, args = %r', args)
        return status

    @no_dry_run
    def power_cycle(self, force):
        self._ipmitool(['chassis', 'bootdev', 'pxe'])
        if force:
            op = 'reset'
        else:
            op = 'cycle'
        if self._ipmitool(['chassis', 'power', op]) == 0:
            return
        if self._ipmitool(['chassis', 'power', 'on']) == 0:
            # power cycle will fail if the machine is not running.
            # To avoid such a situation, just turn it on anyways.
            # Doing this saves power by turning things off without
            # Without breaking the HIL.
            return
        # If it is still does not work, then it is a real error:
        raise OBMError('Could not power cycle node %s' % self.node.label)

    @no_dry_run
    def power_off(self):
        if self._ipmitool(['chassis', 'power', 'off']) != 0:
            raise OBMError('Could not power off node %s', self.label)

    def require_legal_bootdev(self, dev):
        if dev not in self.valid_bootdevices:
            raise BadArgumentError('Invald boot device')

    @no_dry_run
    def set_bootdev(self, dev):
        self.require_legal_bootdev(dev)
        if self._ipmitool(['chassis', 'bootdev', dev, 'options=persistent'
                           ]) != 0:
            raise OBMError('Could not set boot device')

    @no_dry_run
    def start_console(self):
        """Starts logging the IPMI console."""

        # stdin and stderr are redirected to a PIPE that is never read in order
        # to prevent stdout from becoming garbled.  This happens because
        # ipmitool sets shell settings to behave like a tty when communicateing
        # over Serial over Lan
        Popen([
            'ipmitool', '-H', self.host, '-U', self.user, '-P', self.password,
            '-I', 'lanplus', 'sol', 'activate'
        ],
              stdin=PIPE,
              stdout=open(self.get_console_log_filename(), 'a'),
              stderr=PIPE)

    # stdin, stdout, and stderr are redirected to a pipe that is never read
    # because we are not interested in the ouput of this command.
    @no_dry_run
    def stop_console(self):
        call(['pkill', '-f', 'ipmitool -H %s' % self.host])
        proc = Popen([
            'ipmitool', '-H', self.host, '-U', self.user, '-P', self.password,
            '-I', 'lanplus', 'sol', 'deactivate'
        ],
                     stdin=PIPE,
                     stdout=PIPE,
                     stderr=PIPE)
        proc.wait()

    def delete_console(self):
        if os.path.isfile(self.get_console_log_filename()):
            os.remove(self.get_console_log_filename())

    def get_console(self):
        if not os.path.isfile(self.get_console_log_filename()):
            return None
        with open(self.get_console_log_filename(), 'r') as log:
            return "".join(i for i in log.read() if ord(i) < 128)

    def get_console_log_filename(self):
        return '/var/run/hil_console_logs/%s.log' % self.host
Ejemplo n.º 16
0
class DellNOS9(Switch, SwitchSession):
    """Dell S3048-ON running Dell NOS9"""
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/dellnos9'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    interface_type = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        Schema({
            'hostname': basestring,
            'username': basestring,
            'password': basestring,
            'interface_type': basestring,
        }).validate(kwargs)

    def session(self):
        return self

    def ensure_legal_operation(self, nic, op_type, channel):
        check_native_networks(nic, op_type, channel)

    def get_capabilities(self):
        return []

    @staticmethod
    def validate_port_name(port):
        """Valid port names for this switch are of the form 1/0/1 or 1/2"""

        if not re.match(r'^\d+/\d+(/\d+)?$', port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the form 1/0/1 or 1/2")
        return

    def disconnect(self):
        """Since the switch is not connection oriented, we don't need to
        establish a session or disconnect from it."""

    def modify_port(self, port, channel, new_network):
        (port, ) = filter(lambda p: p.label == port, self.ports)
        interface = port.label

        if channel == 'vlan/native':
            if new_network is None:
                self._remove_native_vlan(interface)
                self._port_shutdown(interface)
            else:
                self._set_native_vlan(interface, new_network)
        else:
            vlan_id = channel.replace('vlan/', '')
            legal = get_network_allocator(). \
                is_legal_channel_for(channel, vlan_id)
            assert legal, "HIL passed an invalid channel to the switch!"

            if new_network is None:
                self._remove_vlan_from_trunk(interface, vlan_id)
            else:
                assert new_network == vlan_id
                self._add_vlan_to_trunk(interface, vlan_id)
        if should_save(self):
            self.save_running_config()

    def revert_port(self, port):
        self._remove_all_vlans_from_trunk(port)
        if self._get_native_vlan(port) is not None:
            self._remove_native_vlan(port)
        self._port_shutdown(port)
        if should_save(self):
            self.save_running_config()

    def get_port_networks(self, ports):
        response = {}
        for port in ports:
            response[port] = self._get_vlans(port.label)
            native = self._get_native_vlan(port.label)
            if native is not None:
                response[port].append(native)

        return response

    def _get_vlans(self, interface):
        """ Return the vlans of a trunk port.

        Does not include the native vlan. Use _get_native_vlan.

        Args:
            interface: interface to return the vlans of

        Returns: List containing the vlans of the form:
        [('vlan/vlan1', vlan1), ('vlan/vlan2', vlan2)]
        """

        # It uses the REST API CLI which is slow but it is the only way
        # because the switch is VLAN centric. Doing a GET on interface won't
        # return the VLANs on it, we would have to do get on all vlans (if that
        # worked reliably in the first place) and then find our interface there
        # which is not feasible.

        if not self._is_port_on(interface):
            return []
        response = self._get_port_info(interface)

        # finds a comma separated list of integers and/or ranges starting with
        # T. Sample T12,14-18,23,28,80-90 or T20 or T20,22 or T20-22
        match = re.search(r'T(\d+(-\d+)?)(,\d+(-\d+)?)*', response)
        if match is None:
            return []

        vlan_list = parse_vlans(match.group().replace('T', ''))

        return [('vlan/%s' % x, x) for x in vlan_list]

    def _get_native_vlan(self, interface):
        """ Return the native vlan of an interface.

        Args:
            interface: interface to return the native vlan of

        Returns: Tuple of the form ('vlan/native', vlan) or None

        Similar to _get_vlans()
        """
        if not self._is_port_on(interface):
            return None
        response = self._get_port_info(interface)
        match = re.search(r'NativeVlanId:(\d+)\.', response)
        if match is not None:
            vlan = match.group(1)
        else:
            logger.error('Unexpected: No native vlan found')
            return

        return ('vlan/native', vlan)

    def _get_port_info(self, interface):
        """Returns the output of a show interface command. This removes all
        spaces from the response before returning it which is then parsed by
        the caller.

        Sample Response:
        u"<outputxmlns='http://www.dell.com/ns/dell:0.1/root'>\n
        <command>show interfaces switchport GigabitEthernet1/3\r\n\r\n
        Codes: U-Untagged T-Tagged\r\n x-Dot1x untagged,X-Dot1xtagged\r\n
        G-GVRP tagged,M-Trunk\r\n i-Internal untagged, I-Internaltagged,
        v-VLTuntagged, V-VLTtagged\r\n\r\n Name:GigabitEthernet1/3\r\n 802.1Q
        Tagged:Hybrid\r\n Vlan membership:\r\n Q Vlans\r\n U 1512 \r\n T 1511
        1612-1614,1700\r\n\r\n Native Vlan Id: 1512.\r\n\r\n\r\n\r\n
        MOC-Dell-S3048-ON#</command>\n</output>\n"
        """
        command = 'interfaces switchport %s %s' % \
            (self.interface_type, interface)
        response = self._execute(SHOW, command)
        return response.text.replace(' ', '')

    def _add_vlan_to_trunk(self, interface, vlan):
        """ Add a vlan to a trunk port.

        If the port is not trunked, its mode will be set to trunk.

        Args:
            interface: interface to add the vlan to
            vlan: vlan to add
        """
        if not self._is_port_on(interface):
            self._port_on(interface)
        command = 'interface vlan ' + vlan + '\r\n tagged ' + \
            self.interface_type + ' ' + interface
        self._execute(CONFIG, command)

    def _remove_vlan_from_trunk(self, interface, vlan):
        """ Remove a vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
            vlan: vlan to remove
        """
        command = self._remove_vlan_command(interface, vlan)
        self._execute(CONFIG, command)

    def _remove_all_vlans_from_trunk(self, interface):
        """ Remove all vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
        """
        command = ''
        for vlan in self._get_vlans(interface):
            command += self._remove_vlan_command(interface, vlan[1]) + '\r\n '
        # execute command only if there are some vlans to remove, otherwise
        # the switch complains
        if command is not '':
            self._execute(CONFIG, command)

    def _remove_vlan_command(self, interface, vlan):
        """Returns command to remove <vlan> from <interface>"""
        return 'interface vlan ' + vlan + '\r\n no tagged ' + \
            self.interface_type + ' ' + interface

    def _set_native_vlan(self, interface, vlan):
        """ Set the native vlan of an interface.

        Args:
            interface: interface to set the native vlan to
            vlan: vlan to set as the native vlan

        Method relies on the REST API CLI which is slow
        """
        if not self._is_port_on(interface):
            self._port_on(interface)
        command = 'interface vlan ' + vlan + '\r\n untagged ' + \
            self.interface_type + ' ' + interface
        self._execute(CONFIG, command)

    def _remove_native_vlan(self, interface):
        """ Remove the native vlan from an interface.

        Args:
            interface: interface to remove the native vlan from.vlan
        """
        try:
            vlan = self._get_native_vlan(interface)[1]
            command = 'interface vlan ' + vlan + '\r\n no untagged ' + \
                self.interface_type + ' ' + interface
            self._execute(CONFIG, command)
        except TypeError:
            logger.error('No native vlan to remove')

    def _port_shutdown(self, interface):
        """ Shuts down <interface>

        Turn off portmode hybrid, disable switchport, and then shut down the
        port. All non-default vlans must be removed before calling this.
        """

        url = self._construct_url(interface=interface)
        interface = self._convert_interface_type(self.interface_type) + \
            interface.replace('/', '-')
        payload = '<interface><name>%s</name><portmode><hybrid>false' \
                  '</hybrid></portmode><shutdown>true</shutdown>' \
                  '</interface>' % interface

        self._make_request('PUT', url, data=payload)

    def _port_on(self, interface):
        """ Turns on <interface>

        Turn on port and enable hybrid portmode and switchport.
        """

        url = self._construct_url(interface=interface)
        interface = self._convert_interface_type(self.interface_type) + \
            interface.replace('/', '-')
        payload = '<interface><name>%s</name><portmode><hybrid>true' \
                  '</hybrid></portmode><switchport></switchport>' \
                  '<shutdown>false</shutdown></interface>' % interface

        self._make_request('PUT', url, data=payload)

    def _is_port_on(self, port):
        """ Returns a boolean that tells the status of a switchport"""

        # the url here requires a suffix to GET the shutdown tag in response.
        url = self._construct_url(interface=port) + r'\?with-defaults'
        response = self._make_request('GET', url)
        root = etree.fromstring(response.text)
        shutdown = root.find(self._construct_tag('shutdown')).text

        assert shutdown in ('false', 'true'), "unexpected state of switchport"
        return shutdown == 'false'

    def save_running_config(self):
        command = 'write'
        self._execute(EXEC, command)

    def get_config(self, config_type):
        command = config_type + '-config'
        config = self._execute(SHOW, command).text

        # The config files always have some lines in the beginning that we
        # need to remove otherwise the comparison would fail. Here's a sample:
        # Current Configuration ...
        # ! Version 9.11(0.0P6)
        # ! Last configuration change at Fri Nov  3 23:51:01 2017 by smartuser
        # ! Startup-config last updated at Sat Nov  4 02:04:57 2017 by admin
        # !
        # boot system stack-unit 1 primary system://A
        # boot system stack-unit 1 secondary system://B
        # !
        # hostname MOC-Dell-S3048-ON
        # !
        # protocol lldp
        # !
        # redundancy auto-synchronize full
        # !
        # username xxxxx password 7 XXXXXXXx privilege 15
        # !
        # stack-unit 1 provision S3048-ON

        lines_to_remove = 0
        lines = config.splitlines()
        for line in lines:
            if 'username' in line:
                break
            lines_to_remove += 1

        config = '\n'.join(lines[lines_to_remove:])
        # there were some extra spaces in one of the config file types that
        # would cause the tests to fail.
        return config.replace(" ", "")

    # HELPER METHODS *********************************************

    def _execute(self, command_type, command):
        """This method gets the url & the payload and executes <command>"""
        url = self._construct_url()
        payload = self._make_payload(command_type, command)
        return self._make_request('POST', url, data=payload)

    def _construct_url(self, interface=None):
        """ Construct the API url for a specific interface.

        Args:
            interface: interface to construct the url for

        Returns: string with the url for a specific interface and operation

        If interface is None, then it returns the URL for REST API CLI.
        """

        if interface is None:
            return '%s/api/running/dell/_operations/cli' % self.hostname

        try:
            self.validate_port_name(interface)
            # if `interface` refers to port name
            # the urls have dashes instead of slashes in interface names
            interface = interface.replace('/', '-')
            interface_type = self._convert_interface_type(self.interface_type)
        except BadArgumentError:
            # interface refers to `vlan`
            interface_type = 'vlan-'

        return ''.join([
            self.hostname, '/api/running/dell/interfaces/'
            'interface/', interface_type, interface
        ])

    @staticmethod
    def _convert_interface_type(interface_type):
        """ Convert the interface type from switch CLI form to what the API
        server understands.

        Args:
            interface: the interface in the CLI-form

        Returns: string interface
        """
        iftypes = {
            'GigabitEthernet': 'gige-',
            'TenGigabitEthernet': 'tengig-',
            'TwentyfiveGigabitEthernet': 'twentyfivegig-',
            'fortyGigE': 'fortygig-',
            'peGigabitEthernet': 'pegig-',
            'FiftyGigabitEthernet': 'fiftygig-',
            'HundredGigabitEthernet': 'hundredgig-'
        }

        return iftypes[interface_type]

    @property
    def _auth(self):
        return self.username, self.password

    @staticmethod
    def _make_payload(command_type, command):
        """Makes payload for passing CLI commands using the REST API"""

        return '<input><%s>%s</%s></input>' % (command_type, command,
                                               command_type)

    @staticmethod
    def _construct_tag(name):
        """ Construct the xml tag by prepending the dell tag prefix. """
        return '{http://www.dell.com/ns/dell:0.1/root}%s' % name

    def _make_request(self, method, url, data=None):
        r = requests.request(method, url, data=data, auth=self._auth)
        if r.status_code >= 400:
            logger.error('Bad Request to switch. Response: %s', r.text)
        return r
Ejemplo n.º 17
0
class Brocade(Switch, _vlan_http.Session):
    """Brocade switch"""

    api_name = 'http://schema.massopencloud.org/haas/v0/switches/brocade'

    __mapper_args__ = {
        'polymorphic_identity': api_name,
    }

    id = db.Column(BigIntegerType,
                   db.ForeignKey('switch.id'),
                   primary_key=True)
    hostname = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)
    interface_type = db.Column(db.String, nullable=False)

    @staticmethod
    def validate(kwargs):
        Schema({
            'hostname': basestring,
            'username': basestring,
            'password': basestring,
            'interface_type': basestring,
        }).validate(kwargs)

    def session(self):
        return self

    def ensure_legal_operation(self, nic, op_type, channel):
        check_native_networks(nic, op_type, channel)

    @staticmethod
    def validate_port_name(port):
        """Valid port names for this switch are of the form 1/0/1 or 1/2"""

        val = re.compile(r'^\d+/\d+(/\d+)?$')
        if not val.match(port):
            raise BadArgumentError("Invalid port name. Valid port names for "
                                   "this switch are of the from 1/0/1 or 1/2")
        return

    def get_capabilities(self):
        return []

    def _port_shutdown(self, interface):
        """Shuts down port; unimplemented right now
        TODO: Implement this and associated methods see #970"""

    def _get_mode(self, interface):
        """ Return the mode of an interface.

        Args:
            interface: interface to return the mode of

        Returns: 'access' or 'trunk'

        Raises: AssertionError if mode is invalid.

        """
        url = self._construct_url(interface, suffix='mode')
        response = self._make_request('GET', url)
        root = etree.fromstring(response.text)
        mode = root.find(self._construct_tag('vlan-mode')).text
        return mode

    def _enable_and_set_mode(self, interface, mode):
        """ Enables switching and sets the mode of an interface.

        Args:
            interface: interface to set the mode of
            mode: 'access' or 'trunk'

        Raises: AssertionError if mode is invalid.

        """
        # Enable switching
        url = self._construct_url(interface)
        payload = '<switchport></switchport>'
        self._make_request('POST',
                           url,
                           data=payload,
                           acceptable_error_codes=(409, ))

        # Set the interface mode
        if mode in ['access', 'trunk']:
            url = self._construct_url(interface, suffix='mode')
            payload = '<mode><vlan-mode>%s</vlan-mode></mode>' % mode
            self._make_request('PUT', url, data=payload)
        else:
            raise AssertionError('Invalid mode')

    def _get_vlans(self, interface):
        """ Return the vlans of a trunk port.

        Does not include the native vlan. Use _get_native_vlan.

        Args:
            interface: interface to return the vlans of

        Returns: List containing the vlans of the form:
        [('vlan/vlan1', vlan1), ('vlan/vlan2', vlan2)]
        """
        try:
            url = self._construct_url(interface, suffix='trunk')
            response = self._make_request('GET', url)
            root = etree.fromstring(response.text)
            vlans = root. \
                find(self._construct_tag('allowed')).\
                find(self._construct_tag('vlan')).\
                find(self._construct_tag('add')).text

            # finds a comma separated list of integers and/or ranges.
            # Sample: 12,14-18,23,28,80-90 or 20 or 20,22 or 20-22
            match = re.search(r'(\d+(-\d+)?)(,\d+(-\d+)?)*', vlans)
            if match is None:
                return []

            vlan_list = parse_vlans(match.group())

            return [('vlan/%s' % x, x) for x in vlan_list]
        except AttributeError:
            return []

    def _get_native_vlan(self, interface):
        """ Return the native vlan of an interface.

        Args:
            interface: interface to return the native vlan of

        Returns: Tuple of the form ('vlan/native', vlan) or None
        """
        try:
            url = self._construct_url(interface, suffix='trunk')
            response = self._make_request('GET', url)
            root = etree.fromstring(response.text)
            vlan = root.find(self._construct_tag('native-vlan')).text
            return ('vlan/native', vlan)
        except AttributeError:
            return None

    def _add_vlan_to_trunk(self, interface, vlan):
        """ Add a vlan to a trunk port.

        If the port is not trunked, its mode will be set to trunk.

        Args:
            interface: interface to add the vlan to
            vlan: vlan to add
        """
        self._enable_and_set_mode(interface, 'trunk')
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><add>%s</vlan></vlan>' % vlan
        self._make_request('PUT', url, data=payload)

    def _remove_vlan_from_trunk(self, interface, vlan):
        """ Remove a vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
            vlan: vlan to remove
        """
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><remove>%s</remove></vlan>' % vlan
        self._make_request('PUT', url, data=payload)

    def _remove_all_vlans_from_trunk(self, interface):
        """ Remove all vlan from a trunk port.

        Args:
            interface: interface to remove the vlan from
        """
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><none>true</none></vlan>'
        requests.put(url, data=payload, auth=self._auth)

    def _set_native_vlan(self, interface, vlan):
        """ Set the native vlan of an interface.

        Args:
            interface: interface to set the native vlan to
            vlan: vlan to set as the native vlan
        """
        self._enable_and_set_mode(interface, 'trunk')
        self._disable_native_tag(interface)
        url = self._construct_url(interface, suffix='trunk')
        payload = '<trunk><native-vlan>%s</native-vlan></trunk>' % vlan
        self._make_request('PUT', url, data=payload)

    def _remove_native_vlan(self, interface):
        """ Remove the native vlan from an interface.

        Args:
            interface: interface to remove the native vlan from
        """
        url = self._construct_url(interface, suffix='trunk/native-vlan')
        self._make_request('DELETE', url)

    def _disable_native_tag(self, interface):
        """ Disable tagging of the native vlan

        Args:
            interface: interface to disable the native vlan tagging of

        """
        url = self._construct_url(interface, suffix='trunk/tag/native-vlan')
        self._make_request('DELETE', url, acceptable_error_codes=(404, ))

    def _construct_url(self, interface, suffix=''):
        """ Construct the API url for a specific interface appending suffix.

        Args:
            interface: interface to construct the url for
            suffix: suffix to append at the end of the url

        Returns: string with the url for a specific interface and operation
        """
        # %22 is the encoding for double quotes (") in urls.
        # % escapes the % character.
        # Double quotes are necessary in the url because switch ports contain
        # forward slashes (/), ex. 101/0/10 is encoded as "101/0/10".
        return '%(hostname)s/rest/config/running/interface/' \
            '%(interface_type)s/%%22%(interface)s%%22%(suffix)s' \
            % {
                  'hostname': self.hostname,
                  'interface_type': self.interface_type,
                  'interface': interface,
                  'suffix': '/switchport/%s' % suffix if suffix else ''
            }

    @staticmethod
    def _construct_tag(name):
        """ Construct the xml tag by prepending the brocade tag prefix. """
        return '{urn:brocade.com:mgmt:brocade-interface}%s' % name