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)
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)
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
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()
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
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