Exemple #1
0
class Nexus(Switch):
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/nexus'

    __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 <= 4096,
                       schema.Use(str)),
        }).validate(kwargs)

    def session(self):
        return _Session.connect(self)
Exemple #2
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 _Session.connect(self)
Exemple #3
0
class Brocade(Switch):
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/brocade'

    __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)
    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 disconnect(self):
        pass

    def modify_port(self, port, channel, network_id):
        # 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 network_id is None:
                self._remove_native_vlan(interface)
            else:
                self._set_native_vlan(interface, network_id)
        else:
            match = re.match(re.compile(r'vlan/(\d+)'), channel)
            assert match is not None, "HaaS passed an invalid channel to the" \
                " switch!"
            vlan_id = match.groups()[0]

            if network_id is None:
                self._remove_vlan_from_trunk(interface, vlan_id)
            else:
                assert network_id == 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
Exemple #4
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,
}))
def user_create(user, password, is_admin=False):
    """Create user with given password.

    If the user already exists, a DuplicateError will be raised.
    """
    get_auth_backend().require_admin()
Exemple #5
0
class Ipmi(Obm):
    valid_bootdevices = ['disk', 'pxe', 'none']

    id = db.Column(db.Integer, 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):
        self._ipmitool(['chassis', 'bootdev', 'pxe'])
        if self._ipmitool(['chassis', 'power', 'cycle']) == 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 HaaS.
            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/haas_console_logs/%s.log' % self.host
Exemple #6
0
class Brocade(Switch):
    api_name = 'http://schema.massopencloud.org/haas/v0/switches/brocade'

    __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)
    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 disconnect(self):
        pass

    def apply_networking(self, action):
        """ Apply a NetworkingAction to the switch.

        Args:
            action: NetworkingAction to apply to the switch.
        """
        interface = action.nic.port.label
        channel = action.channel

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

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

    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)]) \
                             + self._get_vlans(port)
        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 = requests.get(url, auth=self._auth)
        root = etree.fromstring(response.text)
        mode = root.find(self._construct_tag('vlan-mode')).text
        return mode

    def _set_mode(self, interface, mode):
        """ Set the mode of an interface.

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

        Raises: AssertionError if mode is invalid.

        """
        if mode in ['access', 'trunk']:
            url = self._construct_url(interface, suffix='mode')
            payload = '<mode><vlan-mode>%s</vlan-mode></mode>' % mode
            requests.put(url, data=payload, auth=self._auth)
        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 = requests.get(url, auth=self._auth)
            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 = requests.get(url, auth=self._auth)
            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._set_mode(interface, 'trunk')
        url = self._construct_url(interface, suffix='trunk/allowed/vlan')
        payload = '<vlan><add>%s</vlan></vlan>' % vlan
        requests.put(url, data=payload, auth=self._auth)

    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
        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._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
        requests.put(url, data=payload, auth=self._auth)

    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')
        requests.delete(url, auth=self._auth)

    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')
        response = requests.delete(url, auth=self._auth)

    def _construct_url(self, interface, suffix=None):
        """ 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/switchport/%(suffix)s' \
            % {
                  'hostname': self.hostname,
                  'interface_type': self.interface_type,
                  'interface': interface,
                  'suffix': suffix
            }

    @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