Exemplo n.º 1
0
    def arg_check(cls, device_id: int, json_data: dict) -> dict:
        parsed_args = {'device_id': device_id}
        if not isinstance(device_id, int):
            raise ValueError("'device_id' must be an integer")

        if 'hostname' not in json_data:
            raise ValueError("POST data must include new 'hostname'")
        else:
            if not Device.valid_hostname(json_data['hostname']):
                raise ValueError("Provided hostname is not valid")
            else:
                parsed_args['new_hostname'] = json_data['hostname']

        if 'device_type' not in json_data:
            raise ValueError("POST data must include 'device_type'")
        else:
            try:
                device_type = str(json_data['device_type']).upper()
            except Exception:
                raise ValueError("'device_type' must be a string")

            if DeviceType.has_name(device_type):
                parsed_args['device_type'] = device_type
            else:
                raise ValueError("Invalid 'device_type' provided")

        if 'mlag_peer_id' in json_data or 'mlag_peer_hostname' in json_data:
            if 'mlag_peer_id' not in json_data or 'mlag_peer_hostname' not in json_data:
                raise ValueError(
                    "Both 'mlag_peer_id' and 'mlag_peer_hostname' must be specified"
                )
            if not isinstance(json_data['mlag_peer_id'], int):
                raise ValueError("'mlag_peer_id' must be an integer")
            if not Device.valid_hostname(json_data['mlag_peer_hostname']):
                raise ValueError("Provided 'mlag_peer_hostname' is not valid")
            parsed_args['mlag_peer_id'] = json_data['mlag_peer_id']
            parsed_args['mlag_peer_new_hostname'] = json_data[
                'mlag_peer_hostname']

        if 'neighbors' in json_data and json_data['neighbors'] is not None:
            if isinstance(json_data['neighbors'], list):
                for neighbor in json_data['neighbors']:
                    if not Device.valid_hostname(neighbor):
                        raise ValueError(
                            "Invalid hostname specified in neighbor list")
                parsed_args['neighbors'] = json_data['neighbors']
            else:
                raise ValueError(
                    "Neighbors must be specified as either a list of hostnames,"
                    "an empty list, or not specified at all")
        else:
            parsed_args['neighbors'] = None

        return parsed_args
Exemplo n.º 2
0
def find_mgmtdomain(session, hostnames: List[str]) -> Optional[Mgmtdomain]:
    """Find the corresponding management domain for a pair of
    distribution switches.

    Args:
        hostnames: A list of two hostnames for the distribution switches

    Raises:
        ValueError: On invalid hostnames etc
    """
    if not isinstance(hostnames, list) or not len(hostnames) == 2:
        raise ValueError(
            "hostnames argument must be a list with two device hostnames")
    for hostname in hostnames:
        if not Device.valid_hostname(hostname):
            raise ValueError(f"Argument {hostname} is not a valid hostname")
    try:
        device0 = session.query(Device).filter(
            Device.hostname == hostnames[0]).one()
    except NoResultFound:
        raise ValueError(
            f"hostname {hostnames[0]} not found in device database")
    try:
        device1 = session.query(Device).filter(
            Device.hostname == hostnames[1]).one()
    except NoResultFound:
        raise ValueError(
            f"hostname {hostnames[1]} not found in device database")
    mgmtdomain = session.query(Mgmtdomain).\
        filter(
            ((Mgmtdomain.device_a == device0) & (Mgmtdomain.device_b == device1))
            |
            ((Mgmtdomain.device_a == device1) & (Mgmtdomain.device_b == device0))
        ).one_or_none()
    return mgmtdomain
Exemplo n.º 3
0
    def get(self):
        """ Get settings """
        args = request.args
        hostname = None
        device_type = None
        model = None
        if 'hostname' in args:
            if Device.valid_hostname(args['hostname']):
                hostname = args['hostname']
            else:
                return empty_result('error', "Invalid hostname specified"), 400
            with sqla_session() as session:
                dev: Device = session.query(Device).\
                    filter(Device.hostname == hostname).one_or_none()
                if dev:
                    device_type = dev.device_type
                    model = dev.model
                else:
                    return empty_result('error', "Hostname not found in database"), 400
        if 'device_type' in args:
            if DeviceType.has_name(args['device_type'].upper()):
                device_type = DeviceType[args['device_type'].upper()]
            else:
                return empty_result('error', "Invalid device type specified"), 400

        try:
            settings, settings_origin = get_settings(hostname, device_type, model)
        except Exception as e:
            return empty_result('error', "Error getting settings: {}".format(str(e))), 400

        return empty_result(data={'settings': settings, 'settings_origin': settings_origin})
Exemplo n.º 4
0
def settings_syncstatus(
        updated_settings: set) -> Tuple[Set[DeviceType], Set[str]]:
    """Determine what devices has become unsynchronized after updating
    the settings repository."""
    unsynced_devtypes = set()
    unsynced_hostnames = set()
    filename: str
    for filename in updated_settings:
        basedir = filename.split(os.path.sep)[0]
        if basedir not in DIR_STRUCTURE:
            continue
        if basedir.startswith('global'):
            return {DeviceType.ACCESS, DeviceType.DIST, DeviceType.CORE}, set()
        elif basedir.startswith('fabric'):
            unsynced_devtypes.update({DeviceType.DIST, DeviceType.CORE})
        elif basedir.startswith('access'):
            unsynced_devtypes.add(DeviceType.ACCESS)
        elif basedir.startswith('dist'):
            unsynced_devtypes.add(DeviceType.DIST)
        elif basedir.startswith('core'):
            unsynced_devtypes.add(DeviceType.CORE)
        elif basedir.startswith('devices'):
            try:
                hostname = filename.split(os.path.sep)[1]
                if Device.valid_hostname(hostname):
                    unsynced_hostnames.add(hostname)
            except Exception as e:
                logger.exception(
                    "Error in settings devices directory: {}".format(str(e)))
        else:
            logger.warn(
                "Unhandled settings file found {}, syncstatus not updated".
                format(filename))
    return (unsynced_devtypes, unsynced_hostnames)
Exemplo n.º 5
0
    def get(self, hostname: str):
        """ Get device configuration """
        result = empty_result()
        result['data'] = {'config': None}
        if not Device.valid_hostname(hostname):
            return empty_result(status='error',
                                data=f"Invalid hostname specified"), 400

        try:
            config, template_vars = cnaas_nms.confpush.sync_devices.generate_only(
                hostname)
            result['data']['config'] = {
                'hostname': hostname,
                'generated_config': config,
                'available_variables': template_vars
            }
        except Exception as e:
            logger.exception(
                f"Exception while generating config for device {hostname}")
            return empty_result(
                status='error',
                data="Exception while generating config for device {}: {} {}".
                format(hostname, type(e), str(e))), 500

        return result
Exemplo n.º 6
0
    def post(self, hostname: str):
        """Restore configuration to previous version"""
        json_data = request.get_json()
        apply_kwargs = {'hostname': hostname}
        config = None
        if not Device.valid_hostname(hostname):
            return empty_result(status='error',
                                data=f"Invalid hostname specified"), 400

        if 'job_id' in json_data:
            try:
                job_id = int(json_data['job_id'])
            except Exception:
                return empty_result('error', "job_id must be an integer"), 400
        else:
            return empty_result('error', "job_id must be specified"), 400

        with sqla_session() as session:
            try:
                prev_config_result = Job.get_previous_config(session,
                                                             hostname,
                                                             job_id=job_id)
                failed = prev_config_result['failed']
                if not failed and 'config' in prev_config_result:
                    config = prev_config_result['config']
            except JobNotFoundError as e:
                return empty_result('error', str(e)), 404
            except InvalidJobError as e:
                return empty_result('error', str(e)), 500
            except Exception as e:
                return empty_result('error',
                                    "Unhandled exception: {}".format(e)), 500

        if failed:
            return empty_result(
                'error', "The specified job_id has a failed status"), 400

        if not config:
            return empty_result('error', "No config found in this job"), 500

        if 'dry_run' in json_data and isinstance(json_data['dry_run'], bool) \
                and not json_data['dry_run']:
            apply_kwargs['dry_run'] = False
        else:
            apply_kwargs['dry_run'] = True

        apply_kwargs['config'] = config

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.sync_devices:apply_config',
            when=1,
            scheduled_by=get_jwt_identity(),
            kwargs=apply_kwargs,
        )

        res = empty_result(data=f"Scheduled job to restore {hostname}")
        res['job_id'] = job_id

        return res, 200
Exemplo n.º 7
0
def verify_dir_structure(path: str, dir_structure: dict):
    """Verify that given path complies to given directory structure.
    Raises:
        VerifyPathException
    """
    for item, subitem in dir_structure.items():
        if isinstance(subitem, str) and subitem == 'file':
            filename = os.path.join(path, item)
            if not os.path.isfile(filename):
                if os.path.exists(filename):
                    raise VerifyPathException(
                        f"{filename} is not a regular file")
                else:
                    raise VerifyPathException(f"File {filename} not found")
        elif item is Device:
            for hostname in os.listdir(path):
                hostname_path = os.path.join(path, hostname)
                if not os.path.isdir(hostname_path) or hostname.startswith(
                        '.'):
                    continue
                if not Device.valid_hostname(hostname):
                    continue
                verify_dir_structure(hostname_path, subitem)
        else:
            dirname = os.path.join(path, item)
            if not os.path.isdir(dirname):
                if os.path.exists(dirname):
                    raise VerifyPathException(f"{dirname} is not a directory")
                else:
                    raise VerifyPathException(f"Directory {dirname} not found")

            if subitem:
                verify_dir_structure(os.path.join(path, item),
                                     dir_structure[item])
Exemplo n.º 8
0
    def post(self):
        json_data = request.get_json()
        kwargs: dict = {}
        if 'hostname' in json_data:
            hostname = str(json_data['hostname'])
            if not Device.valid_hostname(hostname):
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' is not a valid hostname"), 400
            with sqla_session() as session:
                dev: Device = session.query(Device).\
                    filter(Device.hostname == hostname).one_or_none()
                if not dev or dev.state != DeviceState.MANAGED:
                    return empty_result(
                        status='error',
                        data=
                        f"Hostname '{hostname}' not found or is not a managed device"
                    ), 400
            kwargs['hostname'] = hostname
            what = hostname
        elif 'device_type' in json_data:
            if DeviceType.has_name(str(json_data['device_type']).upper()):
                kwargs['device_type'] = str(json_data['device_type']).upper()
            else:
                return empty_result(
                    status='error',
                    data=
                    f"Invalid device type '{json_data['device_type']}' specified"
                ), 400
            what = f"{json_data['device_type']} devices"
        elif 'all' in json_data and isinstance(json_data['all'],
                                               bool) and json_data['all']:
            what = "all devices"
        else:
            return empty_result(
                status='error',
                data=f"No devices to synchronize was specified"), 400

        if 'dry_run' in json_data and isinstance(json_data['dry_run'], bool) \
                and not json_data['dry_run']:
            kwargs['dry_run'] = False
        if 'force' in json_data and isinstance(json_data['force'], bool):
            kwargs['force'] = json_data['force']

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.sync_devices:sync_devices',
            when=1,
            kwargs=kwargs)

        res = empty_result(data=f"Scheduled job to synchronize {what}")
        res['job_id'] = job_id

        return res
Exemplo n.º 9
0
    def post(self):
        """ Add a new linknet """
        json_data = request.get_json()
        data = {}
        errors = []
        if 'device_a' in json_data:
            if not Device.valid_hostname(json_data['device_a']):
                errors.append("Invalid hostname specified for device_a")
        else:
            errors.append("Required field hostname_a not found")
        if 'device_b' in json_data:
            if not Device.valid_hostname(json_data['device_b']):
                errors.append("Invalid hostname specified for device_b")
        else:
            errors.append("Required field hostname_b not found")
        if 'device_a_port' not in json_data:
            errors.append("Required field device_a_port not found")
        if 'device_b_port' not in json_data:
            errors.append("Required field device_b_port not found")

        if errors:
            return empty_result(status='error', data=errors), 400

        with sqla_session() as session:
            try:
                new_prefix = find_free_infra_linknet(session)
                new_linknet = Linknet.create_linknet(
                    session, json_data['device_a'], json_data['device_a_port'],
                    json_data['device_b'], json_data['device_b_port'],
                    new_prefix)
                session.add(new_linknet)
                session.commit()
                data = new_linknet.as_dict()
            except Exception as e:
                session.rollback()
                return empty_result(status='error', data=str(e)), 500

        return empty_result(status='success', data=data), 201
Exemplo n.º 10
0
    def post(self, device_id: int):
        if not isinstance(device_id, int):
            return empty_result(status='error',
                                data="'device_id' must be an integer"), 400

        json_data = request.get_json()
        if 'hostname' not in json_data:
            return empty_result(
                status='error',
                data="POST data must include new 'hostname'"), 400
        else:
            if not Device.valid_hostname(json_data['hostname']):
                return empty_result(status='error',
                                    data='Provided hostname is not valid'), 400
            else:
                new_hostname = json_data['hostname']

        if 'device_type' not in json_data:
            return empty_result(
                status='error',
                data="POST data must include 'device_type'"), 400
        else:
            try:
                device_type = str(json_data['device_type']).upper()
            except:
                return empty_result(status='error',
                                    data="'device_type' must be a string"), 400

            if not DeviceType.has_name(device_type):
                return empty_result(status='error',
                                    data="Invalid 'device_type' provided"), 400

        if device_type == DeviceType.ACCESS.name:
            scheduler = Scheduler()
            job_id = scheduler.add_onetime_job(
                'cnaas_nms.confpush.init_device:init_access_device_step1',
                when=1,
                kwargs={
                    'device_id': device_id,
                    'new_hostname': new_hostname
                })

        res = empty_result(
            data=f"Scheduled job to initialize device_id { device_id }")
        res['job_id'] = job_id

        return res
Exemplo n.º 11
0
    def post(self):
        """ Start update facts of device(s) """
        json_data = request.get_json()
        kwargs: dict = {}

        total_count: Optional[int] = None

        if 'hostname' in json_data:
            hostname = str(json_data['hostname'])
            if not Device.valid_hostname(hostname):
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' is not a valid hostname"), 400
            with sqla_session() as session:
                dev: Device = session.query(Device). \
                    filter(Device.hostname == hostname).one_or_none()
                if not dev or (dev.state != DeviceState.MANAGED
                               and dev.state != DeviceState.UNMANAGED):
                    return empty_result(
                        status='error',
                        data=
                        f"Hostname '{hostname}' not found or is in invalid state"
                    ), 400
            kwargs['hostname'] = hostname
            total_count = 1
        else:
            return empty_result(
                status='error',
                data="No target to be updated was specified"), 400

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.update:update_facts',
            when=1,
            scheduled_by=get_jwt_identity(),
            kwargs=kwargs)

        res = empty_result(
            data=f"Scheduled job to update facts for {hostname}")
        res['job_id'] = job_id

        resp = make_response(json.dumps(res), 200)
        if total_count:
            resp.headers['X-Total-Count'] = total_count
        resp.headers['Content-Type'] = "application/json"
        return resp
Exemplo n.º 12
0
def get_evpn_spines(session, settings: dict):
    logger = get_logger()
    device_hostnames = []
    for entry in settings['evpn_peers']:
        if 'hostname' in entry and Device.valid_hostname(entry['hostname']):
            device_hostnames.append(entry['hostname'])
        else:
            logger.error(
                "Invalid entry specified in settings->evpn_peers, ignoring: {}"
                .format(entry))
    ret = []
    for hostname in device_hostnames:
        dev = session.query(Device).filter(
            Device.hostname == hostname).one_or_none()
        if dev:
            ret.append(dev)
    return ret
Exemplo n.º 13
0
def get_evpn_peers(session, settings: dict):
    logger = get_logger()
    device_hostnames = []
    for entry in settings['evpn_peers']:
        if 'hostname' in entry and Device.valid_hostname(entry['hostname']):
            device_hostnames.append(entry['hostname'])
        else:
            logger.error("Invalid entry specified in settings->evpn_peers, ignoring: {}".format(entry))
    ret = []
    for hostname in device_hostnames:
        dev = session.query(Device).filter(Device.hostname == hostname).one_or_none()
        if dev:
            ret.append(dev)
    # If no evpn_peers were specified return a list of all CORE devices instead
    if not ret:
        core_devs = session.query(Device).filter(Device.device_type == DeviceType.CORE).all()
        for dev in core_devs:
            ret.append(dev)
    return ret
Exemplo n.º 14
0
    def get(self, hostname: str):
        args = request.args
        result = empty_result()
        result['data'] = {'config': None}
        if not Device.valid_hostname(hostname):
            return empty_result(status='error',
                                data=f"Invalid hostname specified"), 400

        kwargs = {}
        if 'job_id' in args:
            try:
                kwargs['job_id'] = int(args['job_id'])
            except Exception:
                return empty_result('error', "job_id must be an integer"), 400
        elif 'previous' in args:
            try:
                kwargs['previous'] = int(args['previous'])
            except Exception:
                return empty_result('error',
                                    "previous must be an integer"), 400
        elif 'before' in args:
            try:
                kwargs['before'] = datetime.datetime.fromisoformat(
                    args['before'])
            except Exception:
                return empty_result(
                    'error',
                    "before must be a valid ISO format date time string"), 400

        with sqla_session() as session:
            try:
                result['data'] = Job.get_previous_config(
                    session, hostname, **kwargs)
            except JobNotFoundError as e:
                return empty_result('error', str(e)), 404
            except InvalidJobError as e:
                return empty_result('error', str(e)), 500
            except Exception as e:
                return empty_result('error',
                                    "Unhandled exception: {}".format(e)), 500

        return result
Exemplo n.º 15
0
def get_all_mgmtdomains(session, hostname: str) -> List[Mgmtdomain]:
    """
    Get all mgmtdomains for a specific distribution switch.

    Args:
        session: sqla session
        hostname: hostname of distribution switch

    Raises:
        ValueError: on invalid hostname etc
    """
    if not Device.valid_hostname(hostname):
        raise ValueError(f"Argument {hostname} is not a valid hostname")
    try:
        dev = session.query(Device).filter(Device.hostname == hostname).one()
    except NoResultFound:
        raise ValueError(f"hostname {hostname} not found in device database")

    mgmtdomains = session.query(Mgmtdomain). \
        filter((Mgmtdomain.device_a == dev) | (Mgmtdomain.device_b == dev)).all()
    return mgmtdomains
Exemplo n.º 16
0
    def post(self, hostname: str):
        """Apply exact specified configuration to device without using templates"""
        json_data = request.get_json()
        apply_kwargs = {'hostname': hostname}
        allow_live_run = get_apidata()['allow_apply_config_liverun']
        if not Device.valid_hostname(hostname):
            return empty_result(
                status='error',
                data=f"Invalid hostname specified"
            ), 400

        if 'full_config' not in json_data:
            return empty_result('error', "full_config must be specified"), 400

        if 'dry_run' in json_data and isinstance(json_data['dry_run'], bool) \
                and not json_data['dry_run']:
            if allow_live_run:
                apply_kwargs['dry_run'] = False
            else:
                return empty_result('error', "Apply config live_run is not allowed"), 400
        else:
            apply_kwargs['dry_run'] = True

        apply_kwargs['config'] = json_data['full_config']

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.sync_devices:apply_config',
            when=1,
            scheduled_by=get_jwt_identity(),
            kwargs=apply_kwargs,
        )

        res = empty_result(data=f"Scheduled job to apply config {hostname}")
        res['job_id'] = job_id

        return res, 200
Exemplo n.º 17
0
    def post(self):
        """Execute certificate related actions on device"""
        json_data = request.get_json()
        # default args
        kwargs: dict = {}

        if 'action' in json_data and isinstance(json_data['action'], str):
            action = json_data['action'].upper()
        else:
            return empty_result(
                status='error',
                data=f"Required field 'action' was not specified"), 400

        if 'comment' in json_data and isinstance(json_data['comment'], str):
            kwargs['job_comment'] = json_data['comment']
        if 'ticket_ref' in json_data and isinstance(json_data['ticket_ref'],
                                                    str):
            kwargs['job_ticket_ref'] = json_data['ticket_ref']

        total_count: Optional[int] = None
        nr = cnaas_init()

        if 'hostname' in json_data:
            hostname = str(json_data['hostname'])
            if not Device.valid_hostname(hostname):
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' is not a valid hostname"), 400
            _, total_count, _ = inventory_selector(nr, hostname=hostname)
            if total_count != 1:
                return empty_result(
                    status='error',
                    data=
                    f"Hostname '{hostname}' not found or is not a managed device"
                ), 400
            kwargs['hostname'] = hostname
        elif 'group' in json_data:
            group_name = str(json_data['group'])
            if group_name not in get_groups():
                return empty_result(
                    status='error',
                    data='Could not find a group with name {}'.format(
                        group_name))
            kwargs['group'] = group_name
            _, total_count, _ = inventory_selector(nr, group=group_name)
        else:
            return empty_result(status='error',
                                data=f"No devices were specified"), 400

        if action == 'RENEW':
            scheduler = Scheduler()
            job_id = scheduler.add_onetime_job(
                'cnaas_nms.confpush.cert:renew_cert',
                when=1,
                scheduled_by=get_jwt_identity(),
                kwargs=kwargs)

            res = empty_result(data=f"Scheduled job to renew certificates")
            res['job_id'] = job_id

            resp = make_response(json.dumps(res), 200)
            if total_count:
                resp.headers['X-Total-Count'] = total_count
            resp.headers['Content-Type'] = "application/json"
            return resp
        else:
            return empty_result(
                status='error',
                data=f"Unknown action specified: {action}"), 400
Exemplo n.º 18
0
 def post(self):
     json_data = request.get_json()
     data = {}
     errors = []
     with sqla_session() as session:
         if 'device_a' in json_data:
             hostname_a = str(json_data['device_a'])
             if not Device.valid_hostname(hostname_a):
                 errors.append(
                     f"Invalid hostname for device_a: {hostname_a}")
             else:
                 device_a = session.query(Device).\
                     filter(Device.hostname == hostname_a).one_or_none()
                 if not device_a:
                     errors.append(
                         f"Device with hostname {hostname_a} not found")
                 else:
                     data['device_a'] = device_a
         if 'device_b' in json_data:
             hostname_b = str(json_data['device_b'])
             if not Device.valid_hostname(hostname_b):
                 errors.append(
                     f"Invalid hostname for device_b: {hostname_b}")
             else:
                 device_b = session.query(Device).\
                     filter(Device.hostname == hostname_b).one_or_none()
                 if not device_b:
                     errors.append(
                         f"Device with hostname {hostname_b} not found")
                 else:
                     data['device_b'] = device_b
         if 'vlan' in json_data:
             try:
                 vlan_id_int = int(json_data['vlan'])
             except:
                 errors.append('Invalid VLAN received.')
             else:
                 data['vlan'] = vlan_id_int
         if 'ipv4_gw' in json_data:
             try:
                 addr = IPv4Interface(json_data['ipv4_gw'])
                 prefix_len = int(addr.network.prefixlen)
             except:
                 errors.append(('Invalid ipv4_gw received. '
                                'Must be correct IPv4 address with mask'))
             else:
                 if prefix_len <= 31 and prefix_len >= 16:
                     data['ipv4_gw'] = str(addr)
                 else:
                     errors.append(
                         "Bad prefix length for management network: {}".
                         format(prefix_len))
         required_keys = ['device_a', 'device_b', 'vlan', 'ipv4_gw']
         if all([key in data for key in required_keys]):
             new_mgmtd = Mgmtdomain()
             new_mgmtd.device_a = data['device_a']
             new_mgmtd.device_b = data['device_b']
             new_mgmtd.ipv4_gw = data['ipv4_gw']
             new_mgmtd.vlan = data['vlan']
             result = session.add(new_mgmtd)
             return empty_result(result, 200)
         else:
             errors.append("Not all required inputs were found: {}".\
                           format(', '.join(required_keys)))
             return empty_result('error', errors), 400
Exemplo n.º 19
0
    def post(self, device_id: int):
        """ Init a device """
        if not isinstance(device_id, int):
            return empty_result(status='error', data="'device_id' must be an integer"), 400

        json_data = request.get_json()

        if 'hostname' not in json_data:
            return empty_result(status='error', data="POST data must include new 'hostname'"), 400
        else:
            if not Device.valid_hostname(json_data['hostname']):
                return empty_result(
                    status='error',
                    data='Provided hostname is not valid'), 400
            else:
                new_hostname = json_data['hostname']

        if 'device_type' not in json_data:
            return empty_result(status='error', data="POST data must include 'device_type'"), 400
        else:
            try:
                device_type = str(json_data['device_type']).upper()
            except Exception:
                return empty_result(status='error', data="'device_type' must be a string"), 400

            if not DeviceType.has_name(device_type):
                return empty_result(status='error', data="Invalid 'device_type' provided"), 400

        job_kwargs = {
            'device_id': device_id,
            'new_hostname': new_hostname
        }

        if 'mlag_peer_id' in json_data or 'mlag_peer_hostname' in json_data:
            if 'mlag_peer_id' not in json_data or 'mlag_peer_hostname' not in json_data:
                return empty_result(
                    status='error',
                    data="Both 'mlag_peer_id' and 'mlag_peer_hostname' must be specified"), 400
            if not isinstance(json_data['mlag_peer_id'], int):
                return empty_result(status='error', data="'mlag_peer_id' must be an integer"), 400
            if not Device.valid_hostname(json_data['mlag_peer_hostname']):
                return empty_result(
                    status='error',
                    data="Provided 'mlag_peer_hostname' is not valid"), 400
            job_kwargs['mlag_peer_id'] = json_data['mlag_peer_id']
            job_kwargs['mlag_peer_new_hostname'] = json_data['mlag_peer_hostname']

        if device_type == DeviceType.ACCESS.name:
            scheduler = Scheduler()
            job_id = scheduler.add_onetime_job(
                'cnaas_nms.confpush.init_device:init_access_device_step1',
                when=1,
                scheduled_by=get_jwt_identity(),
                kwargs=job_kwargs)
        else:
            return empty_result(status='error', data="Unsupported 'device_type' provided"), 400

        res = empty_result(data=f"Scheduled job to initialize device_id { device_id }")
        res['job_id'] = job_id

        return res
Exemplo n.º 20
0
    def post(self):
        """Update/scan interfaces of device"""
        json_data = request.get_json()
        kwargs: dict = {
            "replace": False,
            "delete_all": False,
            "mlag_peer_hostname": None
        }

        total_count: Optional[int] = None

        if 'hostname' in json_data:
            hostname = str(json_data['hostname'])
            if not Device.valid_hostname(hostname):
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' is not a valid hostname"), 400
            with sqla_session() as session:
                dev: Device = session.query(Device). \
                    filter(Device.hostname == hostname).one_or_none()
                if not dev or (dev.state != DeviceState.MANAGED
                               and dev.state != DeviceState.UNMANAGED):
                    return empty_result(
                        status='error',
                        data=
                        f"Hostname '{hostname}' not found or is in invalid state"
                    ), 400
                if dev.device_type != DeviceType.ACCESS:
                    return empty_result(
                        status='error',
                        data=
                        f"Only devices of type ACCESS has interface database to update"
                    ), 400
            kwargs['hostname'] = hostname
            total_count = 1
        else:
            return empty_result(
                status='error',
                data="No target to be updated was specified"), 400

        if 'mlag_peer_hostname' in json_data:
            mlag_peer_hostname = str(json_data['mlag_peer_hostname'])
            if not Device.valid_hostname(mlag_peer_hostname):
                return empty_result(
                    status='error',
                    data=
                    f"Hostname '{mlag_peer_hostname}' is not a valid hostname"
                ), 400
            with sqla_session() as session:
                dev: Device = session.query(Device). \
                    filter(Device.hostname == mlag_peer_hostname).one_or_none()
                if not dev or (dev.state != DeviceState.MANAGED
                               and dev.state != DeviceState.UNMANAGED):
                    return empty_result(
                        status='error',
                        data=
                        f"Hostname '{mlag_peer_hostname}' not found or is in invalid state"
                    ), 400
                if dev.device_type != DeviceType.ACCESS:
                    return empty_result(
                        status='error',
                        data=
                        f"Only devices of type ACCESS has interface database to update"
                    ), 400
            kwargs['mlag_peer_hostname'] = mlag_peer_hostname

        if 'replace' in json_data and isinstance(json_data['replace'], bool) \
                and json_data['replace']:
            kwargs['replace'] = True

        if 'delete_all' in json_data and isinstance(json_data['delete_all'], bool) \
                and json_data['delete_all']:
            kwargs['delete_all'] = True

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.update:update_interfacedb',
            when=1,
            scheduled_by=get_jwt_identity(),
            kwargs=kwargs)

        res = empty_result(
            data=f"Scheduled job to update interfaces for {hostname}")
        res['job_id'] = job_id

        resp = make_response(json.dumps(res), 200)
        if total_count:
            resp.headers['X-Total-Count'] = total_count
        resp.headers['Content-Type'] = "application/json"
        return resp
Exemplo n.º 21
0
def find_mgmtdomain(session, hostnames: List[str]) -> Optional[Mgmtdomain]:
    """Find the corresponding management domain for a pair of
    distribution switches.

    Args:
        hostnames: A list of two hostnames for the distribution switches

    Raises:
        ValueError: On invalid hostnames etc
    """
    if not isinstance(hostnames, list) or not len(hostnames) == 2:
        raise ValueError(
            "Two uplink devices are required to find a compatible mgmtdomain, got: {}"
            .format(hostnames))
    for hostname in hostnames:
        if not Device.valid_hostname(hostname):
            raise ValueError(f"Argument {hostname} is not a valid hostname")
    try:
        device0: Device = session.query(Device).filter(
            Device.hostname == hostnames[0]).one()
    except NoResultFound:
        raise ValueError(
            f"hostname {hostnames[0]} not found in device database")
    try:
        device1: Device = session.query(Device).filter(
            Device.hostname == hostnames[1]).one()
    except NoResultFound:
        raise ValueError(
            f"hostname {hostnames[1]} not found in device database")

    if device0.device_type == DeviceType.DIST or device1.device_type == DeviceType.DIST:
        if device0.device_type != DeviceType.DIST or device1.device_type != DeviceType.DIST:
            raise ValueError(
                "Both uplink devices must be of same device type: {}, {}".
                format(device0.hostname, device1.hostname))
        try:
            mgmtdomain: Mgmtdomain = session.query(Mgmtdomain).\
                filter(
                    ((Mgmtdomain.device_a == device0) & (Mgmtdomain.device_b == device1))
                    |
                    ((Mgmtdomain.device_a == device1) & (Mgmtdomain.device_b == device0))
                ).one_or_none()
            if not mgmtdomain:
                mgmtdomain: Mgmtdomain = session.query(Mgmtdomain).filter(
                    (Mgmtdomain.device_a.has(
                        Device.device_type == DeviceType.CORE))
                    | (Mgmtdomain.device_b.has(
                        Device.device_type == DeviceType.CORE))).one_or_none()
        except MultipleResultsFound:
            raise Exception(
                "Found multiple possible mgmtdomains, please remove any redundant mgmtdomains"
            )
    elif device0.device_type == DeviceType.ACCESS or device1.device_type == DeviceType.ACCESS:
        if device0.device_type != DeviceType.ACCESS or device1.device_type != DeviceType.ACCESS:
            raise ValueError(
                "Both uplink devices must be of same device type: {}, {}".
                format(device0.hostname, device1.hostname))
        mgmtdomain: Mgmtdomain = find_mgmtdomain_by_ip(session,
                                                       device0.management_ip)
        if mgmtdomain.id != find_mgmtdomain_by_ip(session,
                                                  device1.management_ip).id:
            raise Exception(
                "Uplink access devices have different mgmtdomains: {}, {}".
                format(device0.hostname, device1.hostname))
    else:
        raise Exception("Unknown uplink device type")
    return mgmtdomain
Exemplo n.º 22
0
    def post(self):
        """ Upgrade firmware on device """
        json_data = request.get_json()

        kwargs = dict()
        seconds = 1
        date_format = "%Y-%m-%d %H:%M:%S"
        url = self.firmware_url()

        if 'url' not in json_data and url == '':
            return empty_result(status='error',
                                data='No external address configured for '
                                'HTTPD, please specify one with "url"')

        if 'url' not in json_data:
            kwargs['url'] = url
        else:
            if isinstance(json_data['url'], str):
                kwargs['url'] = json_data['url']
            else:
                return empty_result(status='error',
                                    data='url should be a string')

        if 'activate' in json_data:
            if isinstance(json_data['activate'], bool):
                kwargs['activate'] = json_data['activate']
            else:
                return empty_result(status='error',
                                    data='activate should be a boolean')

        if 'download' in json_data:
            if isinstance(json_data['download'], bool):
                kwargs['download'] = json_data['download']
            else:
                return empty_result(status='error',
                                    data='download should be a boolean')

        if 'reboot' in json_data:
            if isinstance(json_data['reboot'], bool):
                kwargs['reboot'] = json_data['reboot']
            else:
                return empty_result(status='error',
                                    data='reboot should be a boolean')

        if 'pre_flight' in json_data:
            if isinstance(json_data['pre_flight'], bool):
                kwargs['pre_flight'] = json_data['pre_flight']
            else:
                return empty_result(status='error',
                                    data='pre_flight should be a boolean')

        if 'post_flight' in json_data:
            if isinstance(json_data['post_flight'], bool):
                kwargs['post_flight'] = json_data['post_flight']
            else:
                return empty_result(status='error',
                                    data='post_flight should be a boolean')

        if 'post_waittime' in json_data:
            if isinstance(json_data['post_waittime'], int):
                kwargs['post_waittime'] = json_data['post_waittime']
            else:
                return empty_result(status='error',
                                    data='post_waittime should be an integer')

        if 'filename' in json_data:
            if isinstance(json_data['filename'], str):
                kwargs['filename'] = json_data['filename']
            else:
                return empty_result(status='error',
                                    data='filename should be a string')

        total_count: Optional[int] = None
        nr = cnaas_init()

        if 'hostname' in json_data:
            hostname = str(json_data['hostname'])
            if not Device.valid_hostname(hostname):
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' is not a valid hostname"
                ), 400
            _, total_count, _ = inventory_selector(nr, hostname=hostname)
            if total_count != 1:
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' not found or is not a managed device"
                ), 400
            kwargs['hostname'] = hostname
        elif 'group' in json_data:
            group_name = str(json_data['group'])
            if group_name not in get_groups():
                return empty_result(status='error', data='Could not find a group with name {}'.format(group_name))
            kwargs['group'] = group_name
            _, total_count, _ = inventory_selector(nr, group=group_name)
            kwargs['group'] = group_name
        else:
            return empty_result(
                status='error',
                data=f"No devices to upgrade were specified"
            ), 400

        if 'comment' in json_data and isinstance(json_data['comment'], str):
            kwargs['job_comment'] = json_data['comment']
        if 'ticket_ref' in json_data and isinstance(json_data['ticket_ref'], str):
            kwargs['job_ticket_ref'] = json_data['ticket_ref']

        if 'start_at' in json_data:
            try:
                time_start = datetime.strptime(json_data['start_at'],
                                               date_format)
                time_now = datetime.utcnow()

                if time_start < time_now:
                    return empty_result(status='error',
                                        data='start_at must be in the future')
                time_diff = time_start - time_now
                seconds = int(time_diff.total_seconds())
            except Exception as e:
                logger.exception(f'Exception when scheduling job: {e}')
                return empty_result(status='error',
                                    data=f'Invalid date format, should be: {date_format}')

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.firmware:device_upgrade',
            when=seconds,
            scheduled_by=get_jwt_identity(),
            kwargs=kwargs)
        res = empty_result(data='Scheduled job to upgrade devices')
        res['job_id'] = job_id

        resp = make_response(json.dumps(res), 200)
        if total_count:
            resp.headers['X-Total-Count'] = total_count
        resp.headers['Content-Type'] = "application/json"
        return resp
Exemplo n.º 23
0
def _refresh_repo_task(repo_type: RepoType = RepoType.TEMPLATES) -> str:
    """Should only be called by refresh_repo function."""
    with open('/etc/cnaas-nms/repository.yml', 'r') as db_file:
        repo_config = yaml.safe_load(db_file)

    if repo_type == RepoType.TEMPLATES:
        local_repo_path = repo_config['templates_local']
        remote_repo_path = repo_config['templates_remote']
    elif repo_type == RepoType.SETTINGS:
        local_repo_path = repo_config['settings_local']
        remote_repo_path = repo_config['settings_remote']
    else:
        raise ValueError("Invalid repository")

    ret = ''
    changed_files: Set[str] = set()
    try:
        local_repo = Repo(local_repo_path)
        prev_commit = local_repo.commit().hexsha
        diff = local_repo.remotes.origin.pull()
        for item in diff:
            ret += 'Commit {} by {} at {}\n'.format(
                item.commit.name_rev, item.commit.committer,
                item.commit.committed_datetime)
            diff_files = local_repo.git.diff('{}..{}'.format(
                prev_commit, item.commit.hexsha),
                                             name_only=True).split()
            changed_files.update(diff_files)
            prev_commit = item.commit.hexsha
    except (InvalidGitRepositoryError, NoSuchPathError) as e:
        logger.info("Local repository {} not found, cloning from remote".\
                    format(local_repo_path))
        try:
            local_repo = Repo.clone_from(remote_repo_path, local_repo_path)
        except NoSuchPathError as e:
            raise ConfigException("Invalid remote repository {}: {}".format(
                remote_repo_path, str(e)))
        except GitCommandError as e:
            raise ConfigException(
                "Error cloning remote repository {}: {}".format(
                    remote_repo_path, str(e)))

        ret = 'Cloned new from remote. Last commit {} by {} at {}'.format(
            local_repo.head.commit.name_rev, local_repo.head.commit.committer,
            local_repo.head.commit.committed_datetime)

    if repo_type == RepoType.SETTINGS:
        try:
            logger.debug("Clearing redis-lru cache for settings")
            with redis_session() as redis_db:
                cache = RedisLRU(redis_db)
                cache.clear_all_cache()
            get_settings()
            test_devtypes = [
                DeviceType.ACCESS, DeviceType.DIST, DeviceType.CORE
            ]
            for devtype in test_devtypes:
                get_settings(device_type=devtype)
            for hostname in os.listdir(os.path.join(local_repo_path,
                                                    'devices')):
                hostname_path = os.path.join(local_repo_path, 'devices',
                                             hostname)
                if not os.path.isdir(hostname_path) or hostname.startswith(
                        '.'):
                    continue
                if not Device.valid_hostname(hostname):
                    continue
                get_settings(hostname)
            check_settings_collisions()
        except SettingsSyntaxError as e:
            logger.exception("Error in settings repo configuration: {}".format(
                str(e)))
            raise e
        except VlanConflictError as e:
            logger.exception("VLAN conflict in repo configuration: {}".format(
                str(e)))
            raise e
        logger.debug(
            "Files changed in settings repository: {}".format(changed_files))
        updated_devtypes, updated_hostnames = settings_syncstatus(
            updated_settings=changed_files)
        logger.debug(
            "Devicestypes to be marked unsynced after repo refresh: {}".format(
                ', '.join([dt.name for dt in updated_devtypes])))
        logger.debug(
            "Devices to be marked unsynced after repo refresh: {}".format(
                ', '.join(updated_hostnames)))
        with sqla_session() as session:
            devtype: DeviceType
            for devtype in updated_devtypes:
                Device.set_devtype_syncstatus(session,
                                              devtype,
                                              syncstatus=False)
            for hostname in updated_hostnames:
                dev: Device = session.query(Device).\
                    filter(Device.hostname == hostname).one_or_none()
                if dev:
                    dev.synchronized = False
                else:
                    logger.warn(
                        "Settings updated for unknown device: {}".format(
                            hostname))

    if repo_type == RepoType.TEMPLATES:
        logger.debug(
            "Files changed in template repository: {}".format(changed_files))
        updated_devtypes = template_syncstatus(updated_templates=changed_files)
        updated_list = [
            '{}:{}'.format(platform, dt.name)
            for dt, platform in updated_devtypes
        ]
        logger.debug(
            "Devicestypes to be marked unsynced after repo refresh: {}".format(
                ', '.join(updated_list)))
        with sqla_session() as session:
            devtype: DeviceType
            for devtype, platform in updated_devtypes:
                Device.set_devtype_syncstatus(session,
                                              devtype,
                                              platform,
                                              syncstatus=False)

    return ret
Exemplo n.º 24
0
    def put(self, hostname):
        """Take a map of interfaces and associated values to update.
        Example:
            {"interfaces": {"Ethernet1": {"configtype": "ACCESS_AUTO"}}}
        """
        json_data = request.get_json()
        data = {}
        errors = []
        device_settings = None

        with sqla_session() as session:
            dev: Device = session.query(Device).filter(Device.hostname == hostname).one_or_none()
            if not dev:
                return empty_result('error', "Device not found"), 404

            updated = False
            if 'interfaces' in json_data and isinstance(json_data['interfaces'], dict):
                for if_name, if_dict in json_data['interfaces'].items():
                    if not isinstance(if_dict, dict):
                        errors.append("Each interface must have a dict with data to update")
                        continue
                    intf: Interface = session.query(Interface).filter(Interface.device == dev).\
                        filter(Interface.name == if_name).one_or_none()
                    if not intf:
                        errors.append(f"Interface {if_name} not found")
                        continue
                    if intf.data and isinstance(intf.data, dict):
                        intfdata_original = dict(intf.data)
                        intfdata = dict(intf.data)
                    else:
                        intfdata_original = {}
                        intfdata = {}

                    if 'configtype' in if_dict:
                        configtype = if_dict['configtype'].upper()
                        if InterfaceConfigType.has_name(configtype):
                            if intf.configtype != InterfaceConfigType[configtype]:
                                intf.configtype = InterfaceConfigType[configtype]
                                updated = True
                                data[if_name] = {'configtype': configtype}
                        else:
                            errors.append(f"Invalid configtype received: {configtype}")

                    if 'data' in if_dict:
                        # TODO: maybe this validation should be done via
                        #  pydantic if it gets more complex
                        if not device_settings:
                            device_settings, _ = get_settings(hostname, dev.device_type)
                        if 'vxlan' in if_dict['data']:
                            if if_dict['data']['vxlan'] in device_settings['vxlans']:
                                intfdata['vxlan'] = if_dict['data']['vxlan']
                            else:
                                errors.append("Specified VXLAN {} is not present in {}".format(
                                    if_dict['data']['vxlan'], hostname
                                ))
                        if 'untagged_vlan' in if_dict['data']:
                            vlan_id = resolve_vlanid(if_dict['data']['untagged_vlan'], device_settings['vxlans'])
                            if vlan_id:
                                intfdata['untagged_vlan'] = if_dict['data']['untagged_vlan']
                            else:
                                errors.append("Specified VLAN name {} is not present in {}".format(
                                    if_dict['data']['untagged_vlan'], hostname
                                ))
                        if 'tagged_vlan_list' in if_dict['data']:
                            if isinstance(if_dict['data']['tagged_vlan_list'], list):
                                vlan_id_list = resolve_vlanid_list(if_dict['data']['tagged_vlan_list'],
                                                                   device_settings['vxlans'])
                                if len(vlan_id_list) == len(if_dict['data']['tagged_vlan_list']):
                                    intfdata['tagged_vlan_list'] = if_dict['data']['tagged_vlan_list']
                                else:
                                    errors.append("Some VLAN names {} are not present in {}".format(
                                        ", ".join(if_dict['data']['tagged_vlan_list']), hostname
                                    ))
                            else:
                                errors.append("tagged_vlan_list should be of type list, found {}".format(
                                    type(if_dict['data']['tagged_vlan_list'])
                                ))
                        if 'neighbor' in if_dict['data']:
                            if isinstance(if_dict['data']['neighbor'], str) and \
                                    Device.valid_hostname(if_dict['data']['neighbor']):
                                intfdata['neighbor'] = if_dict['data']['neighbor']
                            else:
                                errors.append("Neighbor must be valid hostname, got: {}".format(
                                    if_dict['data']['neighbor']))
                        if 'description' in if_dict['data']:
                            if isinstance(if_dict['data']['description'], str) and \
                                    len(if_dict['data']['description']) <= 64:
                                if if_dict['data']['description']:
                                    intfdata['description'] = if_dict['data']['description']
                                elif 'description' in intfdata:
                                    del intfdata['description']
                            elif if_dict['data']['description'] is None:
                                if 'description' in intfdata:
                                    del intfdata['description']
                            else:
                                errors.append(
                                    "Description must be a string of 0-64 characters for: {}".
                                    format(if_dict['data']['description']))
                        if 'enabled' in if_dict['data']:
                            if type(if_dict['data']['enabled']) == bool:
                                intfdata['enabled'] = if_dict['data']['enabled']
                            else:
                                errors.append(
                                    "Enabled must be a bool, true or false, got: {}".
                                    format(if_dict['data']['enabled']))

                    if intfdata != intfdata_original:
                        intf.data = intfdata
                        updated = True
                        if if_name in data:
                            data[if_name]['data'] = intfdata
                        else:
                            data[if_name] = {'data': intfdata}

            if updated:
                dev.synchronized = False

        if errors:
            if data:
                ret = {'errors': errors, 'updated': data}
            else:
                ret = {'errors': errors}
            return empty_result(status='error', data=ret), 400
        else:
            return empty_result(status='success', data={'updated': data})
Exemplo n.º 25
0
 def put(self, device_id):
     json_data = request.get_json()
     data = {}
     errors = []
     if 'state' in json_data:
         try:
             state = str(json_data['state']).upper()
         except:
             errors.append('Invalid device state received.')
         else:
             if DeviceState.has_name(state):
                 data['state'] = DeviceState[state]
             else:
                 errors.append('Invalid device state received.')
     if 'device_type' in json_data:
         try:
             device_type = str(json_data['device_type']).upper()
         except:
             errors.append('Invalid device type received.')
         else:
             if DeviceType.has_name(device_type):
                 data['device_type'] = DeviceType[device_type]
             else:
                 errors.append('Invalid device type received.')
     if 'management_ip' in json_data:
         if json_data['management_ip'] == None:
             data['management_ip'] = None
         else:
             try:
                 addr = IPv4Address(json_data['management_ip'])
             except:
                 errors.append(
                     'Invalid management_ip received. Must be correct IPv4 address.'
                 )
             else:
                 data['management_ip'] = addr
     if 'dhcp_ip' in json_data:
         if json_data['dhcp_ip'] == None:
             data['dhcp_ip'] = None
         else:
             try:
                 addr = IPv4Address(json_data['dhcp_ip'])
             except:
                 errors.append(
                     'Invalid dhcp_ip received. Must be correct IPv4 address.'
                 )
             else:
                 data['dhcp_ip'] = addr
     if 'hostname' in json_data:
         if Device.valid_hostname(json_data['hostname']):
             data['hostname'] = json_data['hostname']
         else:
             errors.append("Invalid hostname received")
     with sqla_session() as session:
         instance = session.query(Device).filter(
             Device.id == device_id).one_or_none()
         if instance:
             #TODO: auto loop through class members and match
             if 'state' in data:
                 instance.state = data['state']
             if 'device_type' in data:
                 instance.device_type = data['device_type']
             if 'management_ip' in data:
                 instance.management_ip = data['management_ip']
             if 'dhcp_ip' in data:
                 instance.dhcp_ip = data['dhcp_ip']
             if 'hostname' in data:
                 instance.hostname = data['hostname']
         else:
             errors.append('Device not found')
Exemplo n.º 26
0
    def post(self):
        """ Start sync of device(s) """
        json_data = request.get_json()
        # default args
        kwargs: dict = {
            'dry_run': True,
            'auto_push': False,
            'force': False,
            'resync': False
        }

        if 'dry_run' in json_data and isinstance(json_data['dry_run'], bool) \
                and not json_data['dry_run']:
            kwargs['dry_run'] = False
        if 'force' in json_data and isinstance(json_data['force'], bool):
            kwargs['force'] = json_data['force']
        if 'auto_push' in json_data and isinstance(json_data['auto_push'],
                                                   bool):
            kwargs['auto_push'] = json_data['auto_push']
        if 'resync' in json_data and isinstance(json_data['resync'], bool):
            kwargs['resync'] = json_data['resync']
        if 'comment' in json_data and isinstance(json_data['comment'], str):
            kwargs['job_comment'] = json_data['comment']
        if 'ticket_ref' in json_data and isinstance(json_data['ticket_ref'],
                                                    str):
            kwargs['job_ticket_ref'] = json_data['ticket_ref']

        total_count: Optional[int] = None
        nr = cnaas_init()

        if 'hostname' in json_data:
            hostname = str(json_data['hostname'])
            if not Device.valid_hostname(hostname):
                return empty_result(
                    status='error',
                    data=f"Hostname '{hostname}' is not a valid hostname"), 400
            _, total_count, _ = inventory_selector(nr, hostname=hostname)
            if total_count != 1:
                return empty_result(
                    status='error',
                    data=
                    f"Hostname '{hostname}' not found or is not a managed device"
                ), 400
            kwargs['hostnames'] = [hostname]
            what = hostname
        elif 'device_type' in json_data:
            devtype_str = str(json_data['device_type']).upper()
            if DeviceType.has_name(devtype_str):
                kwargs['device_type'] = devtype_str
            else:
                return empty_result(
                    status='error',
                    data=
                    f"Invalid device type '{json_data['device_type']}' specified"
                ), 400
            what = f"{json_data['device_type']} devices"
            _, total_count, _ = inventory_selector(nr,
                                                   resync=kwargs['resync'],
                                                   device_type=devtype_str)
        elif 'group' in json_data:
            group_name = str(json_data['group'])
            if group_name not in get_groups():
                return empty_result(
                    status='error',
                    data='Could not find a group with name {}'.format(
                        group_name))
            kwargs['group'] = group_name
            what = 'group {}'.format(group_name)
            _, total_count, _ = inventory_selector(nr,
                                                   resync=kwargs['resync'],
                                                   group=group_name)
        elif 'all' in json_data and isinstance(json_data['all'],
                                               bool) and json_data['all']:
            what = "all devices"
            _, total_count, _ = inventory_selector(nr, resync=kwargs['resync'])
        else:
            return empty_result(
                status='error',
                data=f"No devices to synchronize were specified"), 400

        scheduler = Scheduler()
        job_id = scheduler.add_onetime_job(
            'cnaas_nms.confpush.sync_devices:sync_devices',
            when=1,
            scheduled_by=get_jwt_identity(),
            kwargs=kwargs)

        res = empty_result(data=f"Scheduled job to synchronize {what}")
        res['job_id'] = job_id

        resp = make_response(json.dumps(res), 200)
        if total_count:
            resp.headers['X-Total-Count'] = total_count
        resp.headers['Content-Type'] = "application/json"
        return resp
Exemplo n.º 27
0
    def post(self):
        """ Add a new linknet """
        json_data = request.get_json()
        data = {}
        errors = []
        if 'device_a' in json_data:
            if not Device.valid_hostname(json_data['device_a']):
                errors.append("Invalid hostname specified for device_a")
            else:
                hostname_a = json_data['device_a']
        else:
            errors.append("Required field hostname_a not found")
        if 'device_b' in json_data:
            if not Device.valid_hostname(json_data['device_b']):
                errors.append("Invalid hostname specified for device_b")
            else:
                hostname_b = json_data['device_b']
        else:
            errors.append("Required field hostname_b not found")
        if 'device_a_port' not in json_data:
            errors.append("Required field device_a_port not found")
        if 'device_b_port' not in json_data:
            errors.append("Required field device_b_port not found")

        new_prefix = None
        if 'prefix' in json_data:
            if json_data['prefix']:
                try:
                    new_prefix = IPv4Network(json_data['prefix'])
                except Exception as e:
                    errors.append("Invalid prefix: {}".format(e))

        if errors:
            return empty_result(status='error', data=errors), 400

        with sqla_session() as session:
            dev_a: Device = session.query(Device).\
                filter(Device.hostname == hostname_a).one_or_none()
            if not dev_a:
                return empty_result(
                    status='error',
                    data=
                    f"Hostname '{hostname_a}' not found or is in invalid state"
                ), 400

            dev_b: Device = session.query(Device). \
                filter(Device.hostname == hostname_b).one_or_none()
            if not dev_b:
                return empty_result(
                    status='error',
                    data=
                    f"Hostname '{hostname_b}' not found or is in invalid state"
                ), 400

            # check if we need an ip prefix for the linknet
            ip_linknet_devtypes = [DeviceType.CORE, DeviceType.DIST]
            if dev_a.device_type in ip_linknet_devtypes and \
                    dev_b.device_type in ip_linknet_devtypes:
                if not new_prefix:
                    new_prefix = find_free_infra_linknet(session)
                if not new_prefix:
                    return empty_result(
                        status='error',
                        data=
                        "Device types requires IP linknets, but no prefix could be found"
                    ), 400

            try:
                new_linknet = Linknet.create_linknet(
                    session, hostname_a, json_data['device_a_port'],
                    hostname_b, json_data['device_b_port'], new_prefix)
                session.add(new_linknet)
                session.commit()
                data = new_linknet.as_dict()
            except Exception as e:
                session.rollback()
                return empty_result(status='error', data=str(e)), 500

        return empty_result(status='success', data=data), 201