Beispiel #1
0
    def post(self, body=None):
        """Accepts edit if load_balancer_id isn't blank or create load balancer
        posts.

        :param load_balancer_id: id of lb
        :param *args: holds the posted json or xml data

        Urls:
           POST /loadbalancers/{load_balancer_id}
           PUT  /loadbalancers

        Notes:
           curl -i -H "Accept: application/json" -X POST \
           -d "data={"name": "my_lb"}" \
           http://dev.server:8080/loadbalancers/100

        Returns: dict
        """
        tenant_id = get_limited_to_project(request.headers)
        if body.nodes == Unset or not len(body.nodes):
            raise ClientSideError("At least one backend node needs to be supplied")

        # When the load balancer is used for Galera, we need to do some
        # sanity checking of the nodes to make sure 1 and only 1 node is
        # defined as the primary node.
        if body.protocol and body.protocol.lower() == "galera":
            is_galera = True
        else:
            is_galera = False
        num_galera_primary_nodes = 0

        for node in body.nodes:
            if node.address == Unset:
                raise ClientSideError("A supplied node has no address")
            if node.port == Unset:
                raise ClientSideError("Node {0} is missing a port".format(node.address))
            if node.port < 1 or node.port > 65535:
                raise ClientSideError("Node {0} port number {1} is invalid".format(node.address, node.port))

            try:
                node.address = ipfilter(node.address, conf.ip_filters)
            except IPOutOfRange:
                raise ClientSideError("IP Address {0} is not allowed as a backend node".format(node.address))
            except:
                raise ClientSideError("IP Address {0} not valid".format(node.address))

            if node.weight != Unset:
                try:
                    weight = int(node.weight)
                except ValueError:
                    raise ClientSideError("Node weight must be an integer")
                if weight < 1 or weight > 256:
                    raise ClientSideError("Node weight must be between 1 and 256")

            is_backup = False
            if node.backup != Unset and node.backup == "TRUE":
                is_backup = True
            if is_galera and not is_backup:
                num_galera_primary_nodes += 1

        # Options defaults
        client_timeout_ms = 30000
        server_timeout_ms = 30000
        connect_timeout_ms = 30000
        connect_retries = 3
        if body.options:
            if body.options.client_timeout != Unset:
                client_timeout_ms = body.options.client_timeout
            if body.options.server_timeout != Unset:
                server_timeout_ms = body.options.server_timeout
            if body.options.connect_timeout != Unset:
                connect_timeout_ms = body.options.connect_timeout
            if body.options.connect_retries != Unset:
                connect_retries = body.options.connect_retries

        # Galera sanity checks
        if is_galera and num_galera_primary_nodes != 1:
            raise ClientSideError("Galera load balancer must have exactly one primary node")

        with db_session() as session:
            lblimit = session.query(Limits.value).filter(Limits.name == "maxLoadBalancers").scalar()
            nodelimit = session.query(Limits.value).filter(Limits.name == "maxNodesPerLoadBalancer").scalar()
            namelimit = session.query(Limits.value).filter(Limits.name == "maxLoadBalancerNameLength").scalar()
            count = (
                session.query(LoadBalancer)
                .filter(LoadBalancer.tenantid == tenant_id)
                .filter(LoadBalancer.status != "DELETED")
                .count()
            )
            ports = session.query(Ports.protocol, Ports.portnum).filter(Ports.enabled == 1).all()

            # Allow per-tenant LB limit, defaulting to the global limit if
            # the per-tenant value is not set.
            tenant_lblimit = (
                session.query(TenantLimits.loadbalancers).filter(TenantLimits.tenantid == tenant_id).scalar()
            )
            if tenant_lblimit:
                lblimit = tenant_lblimit

            if len(body.name) > namelimit:
                session.rollback()
                raise ClientSideError("Length of Load Balancer name too long")
            # TODO: this should probably be a 413, not sure how to do that yet
            if count >= lblimit:
                session.rollback()
                raise OverLimit("Account has hit limit of {0} Load Balancers".format(lblimit))
            if len(body.nodes) > nodelimit:
                session.rollback()
                raise OverLimit("Too many backend nodes supplied (limit is {0})".format(nodelimit))

            device = None
            old_lb = None
            # if we don't have an id then we want to create a new lb
            lb = LoadBalancer()
            lb.tenantid = tenant_id
            lb.name = body.name
            if body.protocol:
                if body.protocol.lower() in ("tcp", "http", "galera"):
                    lb.protocol = body.protocol.upper()
                else:
                    raise ClientSideError("Invalid protocol %s" % body.protocol)
            else:
                lb.protocol = "HTTP"

            if body.port:
                if body.port < 1 or body.port > 65535:
                    raise ClientSideError("Port number {0} is invalid".format(body.port))
                # Make sure the port is valid and enabled
                valid = False
                for item in ports:
                    item = item._asdict()
                    if lb.protocol == item["protocol"].upper() and body.port == item["portnum"]:
                        valid = True
                if valid:
                    lb.port = body.port
                else:
                    raise ClientSideError("Port number {0} is invalid".format(body.port))
            else:
                if lb.protocol == "HTTP":
                    lb.port = 80
                elif lb.protocol == "TCP":
                    lb.port = 443
                elif lb.protocol == "GALERA":
                    lb.port = 3306

            lb.status = "BUILD"
            lb.created = None

            if body.virtualIps == Unset:
                # find free device
                # lock with "for update" so multiple APIs don't grab the same
                # LB
                device = (
                    session.query(Device)
                    .filter(~Device.id.in_(session.query(loadbalancers_devices.c.device)))
                    .filter(Device.status == "OFFLINE")
                    .filter(Device.pingCount == 0)
                    .with_lockmode("update")
                    .first()
                )
                if device is None:
                    session.rollback()
                    raise ExhaustedError("No devices available")

                vip = None
            else:
                virtual_id = body.virtualIps[0].id
                # Make sure virtual ID is actually an int
                try:
                    virtual_id = int(virtual_id)
                except:
                    session.rollback()
                    raise NotFound("Invalid virtual IP provided")
                # This is an additional load balancer
                device = session.query(Device).join(Device.vip).filter(Vip.id == virtual_id).first()

                old_lb = (
                    session.query(LoadBalancer)
                    .join(LoadBalancer.devices)
                    .join(Device.vip)
                    .filter(LoadBalancer.tenantid == tenant_id)
                    .filter(Vip.id == virtual_id)
                    .first()
                )

                if old_lb.status in ImmutableStates:
                    session.rollback()
                    raise ImmutableEntity(
                        "Existing Load Balancer on VIP in a non-ACTIVE state"
                        ", current state: {0}".format(old_lb.status)
                    )

                vip = session.query(Vip).filter(Vip.device == device.id).first()
                if old_lb is None:
                    session.rollback()
                    raise NotFound("Invalid virtual IP provided")

                old_count = (
                    session.query(LoadBalancer)
                    .join(LoadBalancer.devices)
                    .join(Device.vip)
                    .filter(LoadBalancer.tenantid == tenant_id)
                    .filter(Vip.id == virtual_id)
                    .filter(LoadBalancer.port == lb.port)
                    .count()
                )
                if old_count:
                    session.rollback()
                    # Error here, can have only one LB per port on a device
                    raise ClientSideError("Only one load balancer per port allowed per device")

            if body.algorithm:
                lb.algorithm = body.algorithm.upper()
            else:
                lb.algorithm = "ROUND_ROBIN"

            lb.client_timeout = client_timeout_ms
            lb.server_timeout = server_timeout_ms
            lb.connect_timeout = connect_timeout_ms
            lb.connect_retries = connect_retries

            lb.devices = [device]
            # write to database
            session.add(lb)
            session.flush()
            # refresh the lb record so we get the id back
            session.refresh(lb)
            for node in body.nodes:
                if node.condition == "DISABLED":
                    enabled = 0
                    node_status = "OFFLINE"
                else:
                    enabled = 1
                    node_status = "ONLINE"

                if node.backup == "TRUE":
                    backup = 1
                else:
                    backup = 0

                weight = 1
                if node.weight != Unset:
                    weight = node.weight
                out_node = Node(
                    lbid=lb.id,
                    port=node.port,
                    address=node.address,
                    enabled=enabled,
                    status=node_status,
                    weight=weight,
                    backup=backup,
                )
                session.add(out_node)

            # now save the loadbalancer_id to the device and switch its status
            # to build so the monitoring does not trigger early.
            # The gearman message code will switch to ONLINE once we know
            # everything is good
            device.status = "BUILD"
            session.flush()

            return_data = LBResp()
            return_data.id = str(lb.id)
            return_data.name = lb.name
            return_data.protocol = lb.protocol
            return_data.port = str(lb.port)
            return_data.algorithm = lb.algorithm
            return_data.status = lb.status
            return_data.created = lb.created
            return_data.updated = lb.updated
            if vip:
                vip_resp = LBVipResp(
                    address=str(ipaddress.IPv4Address(vip.ip)), id=str(vip.id), type="PUBLIC", ipVersion="IPV4"
                )
            else:
                vip_resp = LBVipResp(address=None, id=None, type="ASSIGNING", ipVersion="IPV4")
            return_data.virtualIps = [vip_resp]
            return_data.nodes = []
            for node in body.nodes:
                if node.weight != Unset and node.weight != 1:
                    out_node = LBRespNode(
                        port=str(node.port), address=node.address, condition=node.condition, weight=weight
                    )
                else:
                    out_node = LBRespNode(port=str(node.port), address=node.address, condition=node.condition)

                return_data.nodes.append(out_node)
            session.commit()
            # trigger gearman client to create new lb
            submit_job("UPDATE", device.name, device.id, lb.id)

            return return_data
Beispiel #2
0
    def post(self, body=None):
        """Adds a new node to the load balancer OR Modify the configuration
        of a node on the load balancer.

        :param load_balancer_id: id of lb
        :param node_id: id of node (optional) when missing a new node is added.
        :param *args: holds the posted json or xml data

        Urls:
           POST	 /loadbalancers/{load_balancer_id}/nodes
           PUT	 /loadbalancers/{load_balancer_id}/nodes/{node_id}

        Returns: dict of the full list of nodes or the details of the single
        node
        """
        tenant_id = get_limited_to_project(request.headers)
        if self.lbid is None:
            raise ClientSideError('Load Balancer ID has not been supplied')

        if body.nodes == Unset or not len(body.nodes):
            raise ClientSideError('No nodes have been supplied')

        for node in body.nodes:
            if node.address == Unset:
                raise ClientSideError(
                    'A supplied node has no address'
                )
            if node.port == Unset:
                raise ClientSideError(
                    'Node {0} is missing a port'.format(node.address)
                )
            if node.port < 1 or node.port > 65535:
                raise ClientSideError(
                    'Node {0} port number {1} is invalid'
                    .format(node.address, node.port)
                )
            try:
                node.address = ipfilter(node.address, conf.ip_filters)
            except IPOutOfRange:
                raise ClientSideError(
                    'IP Address {0} is not allowed as a backend node'
                    .format(node.address)
                )
            except:
                raise ClientSideError(
                    'IP Address {0} not valid'.format(node.address)
                )

            if node.weight != Unset:
                try:
                    weight = int(node.weight)
                except ValueError:
                    raise ClientSideError(
                        'Node weight must be an integer'
                    )
                if weight < 1 or weight > 256:
                    raise ClientSideError(
                        'Node weight must be between 1 and 256'
                    )
        with db_session() as session:
            load_balancer = session.query(LoadBalancer).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.id == self.lbid).\
                filter(LoadBalancer.status != 'DELETED').\
                first()
            if load_balancer is None:
                session.rollback()
                raise NotFound('Load Balancer not found')

            if load_balancer.status in ImmutableStates:
                session.rollback()
                raise ImmutableEntity(
                    'Cannot modify a Load Balancer in a non-ACTIVE state'
                    ', current state: {0}'
                    .format(load_balancer.status)
                )

            load_balancer.status = 'PENDING_UPDATE'

            # check if we are over limit
            nodelimit = session.query(Limits.value).\
                filter(Limits.name == 'maxNodesPerLoadBalancer').scalar()
            nodecount = session.query(Node).\
                filter(Node.lbid == self.lbid).count()
            if (nodecount + len(body.nodes)) > nodelimit:
                session.rollback()
                raise OverLimit(
                    'Command would exceed Load Balancer node limit'
                )

            return_data = LBNodeResp()
            return_data.nodes = []

            is_galera = False
            if load_balancer.protocol.lower() == 'galera':
                is_galera = True

            for node in body.nodes:
                is_backup = False
                if node.backup != Unset and node.backup == 'TRUE':
                    is_backup = True

                # Galera load balancer sanity checking. Only allowed to add
                # backup nodes since a primary is presumably already defined.
                if is_galera and not is_backup:
                    raise ClientSideError(
                        'Galera load balancer may have only one primary node'
                    )
                if node.condition == 'DISABLED':
                    enabled = 0
                    node_status = 'OFFLINE'
                else:
                    enabled = 1
                    node_status = 'ONLINE'
                weight = 1
                if node.weight != Unset:
                    weight = node.weight
                new_node = Node(
                    lbid=self.lbid, port=node.port, address=node.address,
                    enabled=enabled, status=node_status,
                    weight=weight, backup=int(is_backup)
                )
                session.add(new_node)
                session.flush()
                if new_node.enabled:
                    condition = 'ENABLED'
                else:
                    condition = 'DISABLED'
                if weight == 1:
                    return_data.nodes.append(
                        NodeResp(
                            id=new_node.id, port=new_node.port,
                            address=new_node.address, condition=condition,
                            status=new_node.status
                        )
                    )
                else:
                    return_data.nodes.append(
                        NodeResp(
                            id=new_node.id, port=new_node.port,
                            address=new_node.address, condition=condition,
                            status=new_node.status, weight=weight
                        )
                    )

            device = session.query(
                Device.id, Device.name, Device.status
            ).join(LoadBalancer.devices).\
                filter(LoadBalancer.id == self.lbid).\
                first()

            session.commit()
            submit_job(
                'UPDATE', device.name, device.id, self.lbid
            )
            return return_data
Beispiel #3
0
    def post(self, body=None):
        """Adds a new node to the load balancer OR Modify the configuration
        of a node on the load balancer.

        :param load_balancer_id: id of lb
        :param node_id: id of node (optional) when missing a new node is added.
        :param *args: holds the posted json or xml data

        Urls:
           POST	 /loadbalancers/{load_balancer_id}/nodes
           PUT	 /loadbalancers/{load_balancer_id}/nodes/{node_id}

        Returns: dict of the full list of nodes or the details of the single
        node
        """
        tenant_id = get_limited_to_project(request.headers)
        if self.lbid is None:
            raise ClientSideError('Load Balancer ID has not been supplied')

        if body.nodes == Unset or not len(body.nodes):
            raise ClientSideError('No nodes have been supplied')

        for node in body.nodes:
            if node.address == Unset:
                raise ClientSideError('A supplied node has no address')
            if node.port == Unset:
                raise ClientSideError('Node {0} is missing a port'.format(
                    node.address))
            if node.port < 1 or node.port > 65535:
                raise ClientSideError(
                    'Node {0} port number {1} is invalid'.format(
                        node.address, node.port))
            try:
                node.address = ipfilter(node.address, conf.ip_filters)
            except IPOutOfRange:
                raise ClientSideError(
                    'IP Address {0} is not allowed as a backend node'.format(
                        node.address))
            except:
                raise ClientSideError('IP Address {0} not valid'.format(
                    node.address))

            if node.weight != Unset:
                try:
                    weight = int(node.weight)
                except ValueError:
                    raise ClientSideError('Node weight must be an integer')
                if weight < 1 or weight > 256:
                    raise ClientSideError(
                        'Node weight must be between 1 and 256')
        with db_session() as session:
            load_balancer = session.query(LoadBalancer).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.id == self.lbid).\
                filter(LoadBalancer.status != 'DELETED').\
                first()
            if load_balancer is None:
                session.rollback()
                raise NotFound('Load Balancer not found')

            if load_balancer.status in ImmutableStates:
                session.rollback()
                raise ImmutableEntity(
                    'Cannot modify a Load Balancer in a non-ACTIVE state'
                    ', current state: {0}'.format(load_balancer.status))

            load_balancer.status = 'PENDING_UPDATE'

            # check if we are over limit
            nodelimit = session.query(Limits.value).\
                filter(Limits.name == 'maxNodesPerLoadBalancer').scalar()
            nodecount = session.query(Node).\
                filter(Node.lbid == self.lbid).count()
            if (nodecount + len(body.nodes)) > nodelimit:
                session.rollback()
                raise OverLimit(
                    'Command would exceed Load Balancer node limit')

            return_data = LBNodeResp()
            return_data.nodes = []

            is_galera = False
            if load_balancer.protocol.lower() == 'galera':
                is_galera = True

            for node in body.nodes:
                is_backup = False
                if node.backup != Unset and node.backup == 'TRUE':
                    is_backup = True

                # Galera load balancer sanity checking. Only allowed to add
                # backup nodes since a primary is presumably already defined.
                if is_galera and not is_backup:
                    raise ClientSideError(
                        'Galera load balancer may have only one primary node')
                if node.condition == 'DISABLED':
                    enabled = 0
                    node_status = 'OFFLINE'
                else:
                    enabled = 1
                    node_status = 'ONLINE'
                weight = 1
                if node.weight != Unset:
                    weight = node.weight
                new_node = Node(lbid=self.lbid,
                                port=node.port,
                                address=node.address,
                                enabled=enabled,
                                status=node_status,
                                weight=weight,
                                backup=int(is_backup))
                session.add(new_node)
                session.flush()
                if new_node.enabled:
                    condition = 'ENABLED'
                else:
                    condition = 'DISABLED'
                if weight == 1:
                    return_data.nodes.append(
                        NodeResp(id=new_node.id,
                                 port=new_node.port,
                                 address=new_node.address,
                                 condition=condition,
                                 status=new_node.status))
                else:
                    return_data.nodes.append(
                        NodeResp(id=new_node.id,
                                 port=new_node.port,
                                 address=new_node.address,
                                 condition=condition,
                                 status=new_node.status,
                                 weight=weight))

            device = session.query(
                Device.id, Device.name, Device.status
            ).join(LoadBalancer.devices).\
                filter(LoadBalancer.id == self.lbid).\
                first()

            session.commit()
            submit_job('UPDATE', device.name, device.id, self.lbid)
            return return_data
Beispiel #4
0
    def post(self, body=None):
        """Accepts edit if load_balancer_id isn't blank or create load balancer
        posts.

        :param load_balancer_id: id of lb
        :param *args: holds the posted json or xml data

        Urls:
           POST /loadbalancers/{load_balancer_id}
           PUT  /loadbalancers

        Notes:
           curl -i -H "Accept: application/json" -X POST \
           -d "data={"name": "my_lb"}" \
           http://dev.server:8080/loadbalancers/100

        Returns: dict
        """
        tenant_id = get_limited_to_project(request.headers)
        if body.nodes == Unset or not len(body.nodes):
            raise ClientSideError(
                'At least one backend node needs to be supplied')

        # When the load balancer is used for Galera, we need to do some
        # sanity checking of the nodes to make sure 1 and only 1 node is
        # defined as the primary node.
        if body.protocol and body.protocol.lower() == 'galera':
            is_galera = True
        else:
            is_galera = False
        num_galera_primary_nodes = 0

        for node in body.nodes:
            if node.address == Unset:
                raise ClientSideError('A supplied node has no address')
            if node.port == Unset:
                raise ClientSideError('Node {0} is missing a port'.format(
                    node.address))
            if node.port < 1 or node.port > 65535:
                raise ClientSideError(
                    'Node {0} port number {1} is invalid'.format(
                        node.address, node.port))

            try:
                node.address = ipfilter(node.address, conf.ip_filters)
            except IPOutOfRange:
                raise ClientSideError(
                    'IP Address {0} is not allowed as a backend node'.format(
                        node.address))
            except:
                raise ClientSideError('IP Address {0} not valid'.format(
                    node.address))

            if node.weight != Unset:
                try:
                    weight = int(node.weight)
                except ValueError:
                    raise ClientSideError('Node weight must be an integer')
                if weight < 1 or weight > 256:
                    raise ClientSideError(
                        'Node weight must be between 1 and 256')

            is_backup = False
            if node.backup != Unset and node.backup == 'TRUE':
                is_backup = True
            if is_galera and not is_backup:
                num_galera_primary_nodes += 1

        # Options defaults
        client_timeout_ms = 30000
        server_timeout_ms = 30000
        connect_timeout_ms = 30000
        connect_retries = 3
        if body.options:
            if body.options.client_timeout != Unset:
                client_timeout_ms = body.options.client_timeout
            if body.options.server_timeout != Unset:
                server_timeout_ms = body.options.server_timeout
            if body.options.connect_timeout != Unset:
                connect_timeout_ms = body.options.connect_timeout
            if body.options.connect_retries != Unset:
                connect_retries = body.options.connect_retries

        # Galera sanity checks
        if is_galera and num_galera_primary_nodes != 1:
            raise ClientSideError(
                'Galera load balancer must have exactly one primary node')

        with db_session() as session:
            lblimit = session.query(Limits.value).\
                filter(Limits.name == 'maxLoadBalancers').scalar()
            nodelimit = session.query(Limits.value).\
                filter(Limits.name == 'maxNodesPerLoadBalancer').scalar()
            namelimit = session.query(Limits.value).\
                filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
            count = session.query(LoadBalancer).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').count()
            ports = session.query(Ports.protocol, Ports.portnum).\
                filter(Ports.enabled == 1).all()

            # Allow per-tenant LB limit, defaulting to the global limit if
            # the per-tenant value is not set.
            tenant_lblimit = session.query(TenantLimits.loadbalancers).\
                filter(TenantLimits.tenantid == tenant_id).scalar()
            if tenant_lblimit:
                lblimit = tenant_lblimit

            if len(body.name) > namelimit:
                session.rollback()
                raise ClientSideError('Length of Load Balancer name too long')
            # TODO: this should probably be a 413, not sure how to do that yet
            if count >= lblimit:
                session.rollback()
                raise OverLimit(
                    'Account has hit limit of {0} Load Balancers'.format(
                        lblimit))
            if len(body.nodes) > nodelimit:
                session.rollback()
                raise OverLimit(
                    'Too many backend nodes supplied (limit is {0})'.format(
                        nodelimit))

            device = None
            old_lb = None
            # if we don't have an id then we want to create a new lb
            lb = LoadBalancer()
            lb.tenantid = tenant_id
            lb.name = body.name
            if body.protocol:
                if body.protocol.lower() in ('tcp', 'http', 'galera'):
                    lb.protocol = body.protocol.upper()
                else:
                    raise ClientSideError('Invalid protocol %s' %
                                          body.protocol)
            else:
                lb.protocol = 'HTTP'

            if body.port:
                if body.port < 1 or body.port > 65535:
                    raise ClientSideError('Port number {0} is invalid'.format(
                        body.port))
                # Make sure the port is valid and enabled
                valid = False
                for item in ports:
                    item = item._asdict()
                    if (lb.protocol == item["protocol"].upper()
                            and body.port == item["portnum"]):
                        valid = True
                if valid:
                    lb.port = body.port
                else:
                    raise ClientSideError('Port number {0} is invalid'.format(
                        body.port))
            else:
                if lb.protocol == 'HTTP':
                    lb.port = 80
                elif lb.protocol == 'TCP':
                    lb.port = 443
                elif lb.protocol == 'GALERA':
                    lb.port = 3306

            lb.status = 'BUILD'
            lb.created = None

            if body.virtualIps == Unset:
                # find free device
                # lock with "for update" so multiple APIs don't grab the same
                # LB
                device = session.query(Device).\
                    filter(~Device.id.in_(
                        session.query(loadbalancers_devices.c.device)
                    )).\
                    filter(Device.status == "OFFLINE").\
                    filter(Device.pingCount == 0).\
                    with_lockmode('update').\
                    first()
                if device is None:
                    session.rollback()
                    raise ExhaustedError('No devices available')

                vip = None
            else:
                virtual_id = body.virtualIps[0].id
                # Make sure virtual ID is actually an int
                try:
                    virtual_id = int(virtual_id)
                except:
                    session.rollback()
                    raise NotFound('Invalid virtual IP provided')
                # This is an additional load balancer
                device = session.query(
                    Device
                ).join(Device.vip).\
                    filter(Vip.id == virtual_id).\
                    first()

                old_lb = session.query(
                    LoadBalancer
                ).join(LoadBalancer.devices).\
                    join(Device.vip).\
                    filter(LoadBalancer.tenantid == tenant_id).\
                    filter(Vip.id == virtual_id).\
                    first()

                if old_lb.status in ImmutableStates:
                    session.rollback()
                    raise ImmutableEntity(
                        'Existing Load Balancer on VIP in a non-ACTIVE state'
                        ', current state: {0}'.format(old_lb.status))

                vip = session.query(Vip).\
                    filter(Vip.device == device.id).\
                    first()
                if old_lb is None:
                    session.rollback()
                    raise NotFound('Invalid virtual IP provided')

                old_count = session.query(
                    LoadBalancer
                ).join(LoadBalancer.devices).\
                    join(Device.vip).\
                    filter(LoadBalancer.tenantid == tenant_id).\
                    filter(Vip.id == virtual_id).\
                    filter(LoadBalancer.port == lb.port).\
                    count()
                if old_count:
                    session.rollback()
                    # Error here, can have only one LB per port on a device
                    raise ClientSideError(
                        'Only one load balancer per port allowed per device')

            if body.algorithm:
                lb.algorithm = body.algorithm.upper()
            else:
                lb.algorithm = 'ROUND_ROBIN'

            lb.client_timeout = client_timeout_ms
            lb.server_timeout = server_timeout_ms
            lb.connect_timeout = connect_timeout_ms
            lb.connect_retries = connect_retries

            lb.devices = [device]
            # write to database
            session.add(lb)
            session.flush()
            #refresh the lb record so we get the id back
            session.refresh(lb)
            for node in body.nodes:
                if node.condition == 'DISABLED':
                    enabled = 0
                    node_status = 'OFFLINE'
                else:
                    enabled = 1
                    node_status = 'ONLINE'

                if node.backup == 'TRUE':
                    backup = 1
                else:
                    backup = 0

                weight = 1
                if node.weight != Unset:
                    weight = node.weight
                out_node = Node(lbid=lb.id,
                                port=node.port,
                                address=node.address,
                                enabled=enabled,
                                status=node_status,
                                weight=weight,
                                backup=backup)
                session.add(out_node)

            # now save the loadbalancer_id to the device and switch its status
            # to build so the monitoring does not trigger early.
            # The gearman message code will switch to ONLINE once we know
            # everything is good
            device.status = "BUILD"
            session.flush()

            return_data = LBResp()
            return_data.id = str(lb.id)
            return_data.name = lb.name
            return_data.protocol = lb.protocol
            return_data.port = str(lb.port)
            return_data.algorithm = lb.algorithm
            return_data.status = lb.status
            return_data.created = lb.created
            return_data.updated = lb.updated
            if vip:
                vip_resp = LBVipResp(address=str(ipaddress.IPv4Address(
                    vip.ip)),
                                     id=str(vip.id),
                                     type='PUBLIC',
                                     ipVersion='IPV4')
            else:
                vip_resp = LBVipResp(address=None,
                                     id=None,
                                     type='ASSIGNING',
                                     ipVersion='IPV4')
            return_data.virtualIps = [vip_resp]
            return_data.nodes = []
            for node in body.nodes:
                if node.weight != Unset and node.weight != 1:
                    out_node = LBRespNode(port=str(node.port),
                                          address=node.address,
                                          condition=node.condition,
                                          weight=weight)
                else:
                    out_node = LBRespNode(port=str(node.port),
                                          address=node.address,
                                          condition=node.condition)

                return_data.nodes.append(out_node)
            session.commit()
            # trigger gearman client to create new lb
            submit_job('UPDATE', device.name, device.id, lb.id)

            return return_data
Beispiel #5
0
    def post(self, body=None):
        """Accepts edit if load_balancer_id isn't blank or create load balancer
        posts.

        :param load_balancer_id: id of lb
        :param *args: holds the posted json or xml data

        Urls:
           POST /loadbalancers/{load_balancer_id}
           PUT  /loadbalancers

        Notes:
           curl -i -H "Accept: application/json" -X POST \
           -d "data={"name": "my_lb"}" \
           http://dev.server:8080/loadbalancers/100

        Returns: dict
        """
        tenant_id = get_limited_to_project(request.headers)
        if body.nodes == Unset or not len(body.nodes):
            raise ClientSideError(
                'At least one backend node needs to be supplied'
            )

        # When the load balancer is used for Galera, we need to do some
        # sanity checking of the nodes to make sure 1 and only 1 node is
        # defined as the primary node.
        if body.protocol and body.protocol.lower() == 'galera':
            is_galera = True
        else:
            is_galera = False
        num_galera_primary_nodes = 0

        for node in body.nodes:
            if node.address == Unset:
                raise ClientSideError(
                    'A supplied node has no address'
                )
            if node.port == Unset:
                raise ClientSideError(
                    'Node {0} is missing a port'.format(node.address)
                )
            if node.port < 1 or node.port > 65535:
                raise ClientSideError(
                    'Node {0} port number {1} is invalid'
                    .format(node.address, node.port)
                )

            try:
                node.address = ipfilter(node.address, conf.ip_filters)
            except IPOutOfRange:
                raise ClientSideError(
                    'IP Address {0} is not allowed as a backend node'
                    .format(node.address)
                )
            except:
                raise ClientSideError(
                    'IP Address {0} not valid'.format(node.address)
                )

            if node.weight != Unset:
                try:
                    weight = int(node.weight)
                except ValueError:
                    raise ClientSideError(
                        'Node weight must be an integer'
                    )
                if weight < 1 or weight > 256:
                    raise ClientSideError(
                        'Node weight must be between 1 and 256'
                    )

            is_backup = False
            if node.backup != Unset and node.backup == 'TRUE':
                is_backup = True
            if is_galera and not is_backup:
                num_galera_primary_nodes += 1

        # Options defaults
        timeout_ms = self.LB_TIMEOUT_MS
        retries = self.LB_RETRIES
        if body.options:
            if body.options.timeout != Unset:
                try:
                    timeout_ms = int(body.options.timeout)
                    if timeout_ms < 0 or timeout_ms > self.LB_TIMEOUT_MAX:
                        raise ClientSideError(
                            'timeout must be between 0 and {0} ms'
                            .format(self.LB_TIMEOUT_MAX)
                        )
                except ValueError:
                    raise ClientSideError(
                        'timeout must be an integer'
                    )
            if body.options.retries != Unset:
                try:
                    retries = int(body.options.retries)
                    if retries < 0 or retries > self.LB_RETRIES_MAX:
                        raise ClientSideError(
                            'retries must be between 0 and {0}'
                            .format(self.LB_RETRIES_MAX)
                        )
                except ValueError:
                    raise ClientSideError(
                        'retries must be an integer'
                    )

        # Galera sanity checks
        if is_galera and num_galera_primary_nodes != 1:
            raise ClientSideError(
                'Galera load balancer must have exactly one primary node'
            )

        with db_session() as session:
            lblimit = session.query(Limits.value).\
                filter(Limits.name == 'maxLoadBalancers').scalar()
            nodelimit = session.query(Limits.value).\
                filter(Limits.name == 'maxNodesPerLoadBalancer').scalar()
            namelimit = session.query(Limits.value).\
                filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
            count = session.query(LoadBalancer).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').count()
            ports = session.query(Ports.protocol, Ports.portnum).\
                filter(Ports.enabled == 1).all()

            # Allow per-tenant LB limit, defaulting to the global limit if
            # the per-tenant value is not set.
            tenant_lblimit = session.query(TenantLimits.loadbalancers).\
                filter(TenantLimits.tenantid == tenant_id).scalar()
            if tenant_lblimit:
                lblimit = tenant_lblimit

            if len(body.name) > namelimit:
                session.rollback()
                raise ClientSideError(
                    'Length of Load Balancer name too long'
                )
            # TODO: this should probably be a 413, not sure how to do that yet
            if count >= lblimit:
                session.rollback()
                raise OverLimit(
                    'Account has hit limit of {0} Load Balancers'.
                    format(lblimit)
                )
            if len(body.nodes) > nodelimit:
                session.rollback()
                raise OverLimit(
                    'Too many backend nodes supplied (limit is {0})'.
                    format(nodelimit)
                )

            device = None
            old_lb = None
            # if we don't have an id then we want to create a new lb
            lb = LoadBalancer()
            lb.tenantid = tenant_id
            lb.name = body.name
            if body.protocol:
                if body.protocol.lower() in ('tcp', 'http', 'galera'):
                    lb.protocol = body.protocol.upper()
                else:
                    raise ClientSideError(
                        'Invalid protocol %s' % body.protocol
                    )
            else:
                lb.protocol = 'HTTP'

            if body.port:
                if body.port < 1 or body.port > 65535:
                    raise ClientSideError(
                        'Port number {0} is invalid'.format(body.port)
                    )
                # Make sure the port is valid and enabled
                valid = False
                for item in ports:
                    item = item._asdict()
                    if(lb.protocol == item["protocol"].upper() and
                            body.port == item["portnum"]):
                        valid = True
                if valid:
                    lb.port = body.port
                else:
                    raise ClientSideError(
                        'Port number {0} is not allowed for {1} protocol'
                        .format(body.port, lb.protocol)
                    )
            else:
                if lb.protocol == 'HTTP':
                    lb.port = 80
                elif lb.protocol == 'TCP':
                    lb.port = 443
                elif lb.protocol == 'GALERA':
                    lb.port = 3306

            lb.status = 'BUILD'
            lb.created = None

            if body.virtualIps == Unset:
                # find free device
                # lock with "for update" so multiple APIs don't grab the same
                # LB
                device = session.query(Device).\
                    filter(~Device.id.in_(
                        session.query(loadbalancers_devices.c.device)
                    )).\
                    filter(Device.status == "OFFLINE").\
                    filter(Device.pingCount == 0).\
                    with_lockmode('update').\
                    first()
                if device is None:
                    session.rollback()
                    raise ExhaustedError('No devices available')

                vip = None
            else:
                virtual_id = body.virtualIps[0].id
                # Make sure virtual ID is actually an int
                try:
                    virtual_id = int(virtual_id)
                except:
                    session.rollback()
                    raise NotFound('Invalid virtual IP provided')
                # This is an additional load balancer
                device = session.query(
                    Device
                ).join(Device.vip).\
                    filter(Vip.id == virtual_id).\
                    first()

                old_lb = session.query(
                    LoadBalancer
                ).join(LoadBalancer.devices).\
                    join(Device.vip).\
                    filter(LoadBalancer.tenantid == tenant_id).\
                    filter(Vip.id == virtual_id).\
                    first()

                if old_lb.status in ImmutableStates:
                    session.rollback()
                    raise ImmutableEntity(
                        'Existing Load Balancer on VIP in a non-ACTIVE state'
                        ', current state: {0}'
                        .format(old_lb.status)
                    )

                vip = session.query(Vip).\
                    filter(Vip.device == device.id).\
                    first()
                if old_lb is None:
                    session.rollback()
                    raise NotFound('Invalid virtual IP provided')

                old_count = session.query(
                    LoadBalancer
                ).join(LoadBalancer.devices).\
                    join(Device.vip).\
                    filter(LoadBalancer.tenantid == tenant_id).\
                    filter(Vip.id == virtual_id).\
                    filter(LoadBalancer.port == lb.port).\
                    count()
                if old_count:
                    session.rollback()
                    # Error, can have only one LB per port on a device
                    raise ClientSideError(
                        'Only one load balancer per port allowed per device'
                    )

                if lb.protocol == 'HTTP':
                    protocol_count = session.query(
                        LoadBalancer
                    ).join(LoadBalancer.devices).\
                        join(Device.vip).\
                        filter(LoadBalancer.tenantid == tenant_id).\
                        filter(Vip.id == virtual_id).\
                        filter(LoadBalancer.protocol == lb.protocol).\
                        count()
                else:
                    # TCP or GALERA. Both are TCP really
                    protocol_count = session.query(
                        LoadBalancer
                    ).join(LoadBalancer.devices).\
                        join(Device.vip).\
                        filter(LoadBalancer.tenantid == tenant_id).\
                        filter(Vip.id == virtual_id).\
                        filter((LoadBalancer.protocol == 'TCP') |
                               (LoadBalancer.protocol == 'GALERA')).\
                        count()

                if protocol_count:
                    session.rollback()
                    # Error, can have only one LB per protocol on a device
                    raise ClientSideError(
                        'Only one load balancer per protocol'
                        ' allowed per device'
                    )

            if body.algorithm:
                lb.algorithm = body.algorithm.upper()
            else:
                lb.algorithm = 'ROUND_ROBIN'

            lb.timeout = timeout_ms
            lb.retries = retries

            lb.devices = [device]
            # write to database
            session.add(lb)
            session.flush()
            # refresh the lb record so we get the id back
            session.refresh(lb)
            for node in body.nodes:
                if node.condition == 'DISABLED':
                    enabled = 0
                    node_status = 'OFFLINE'
                else:
                    enabled = 1
                    node_status = 'ONLINE'

                if node.backup == 'TRUE':
                    backup = 1
                else:
                    backup = 0

                weight = 1
                if node.weight != Unset:
                    weight = node.weight
                out_node = Node(
                    lbid=lb.id, port=node.port, address=node.address,
                    enabled=enabled, status=node_status,
                    weight=weight, backup=backup
                )
                session.add(out_node)

            # now save the loadbalancer_id to the device and switch its status
            # to build so the monitoring does not trigger early.
            # The gearman message code will switch to ONLINE once we know
            # everything is good
            device.status = "BUILD"
            session.flush()

            return_data = LBResp()
            return_data.id = str(lb.id)
            return_data.name = lb.name
            return_data.protocol = lb.protocol
            return_data.port = str(lb.port)
            return_data.algorithm = lb.algorithm
            return_data.status = lb.status
            return_data.created = lb.created
            return_data.updated = lb.updated
            if vip:
                vip_resp = LBVipResp(
                    address=str(ipaddress.IPv4Address(vip.ip)),
                    id=str(vip.id), type='PUBLIC', ipVersion='IPV4'
                )
            else:
                vip_resp = LBVipResp(
                    address=None, id=None, type='ASSIGNING', ipVersion='IPV4'
                )
            return_data.virtualIps = [vip_resp]

            nodes = session.query(
                Node.id, Node.address, Node.port, Node.status,
                Node.enabled, Node.weight
            ).join(LoadBalancer.nodes).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.id == lb.id).\
                all()

            return_data.nodes = []
            for node in nodes:
                if node.enabled == 1:
                    condition = 'ENABLED'
                else:
                    condition = 'DISABLED'

                if node.weight == 1:
                    return_data.nodes.append(
                        LBRespNode(
                            id=str(node.id), port=str(node.port),
                            address=node.address, condition=condition,
                            status=node.status
                        )
                    )
                else:
                    return_data.nodes.append(
                        LBRespNode(
                            id=str(node.id), port=str(node.port),
                            address=node.address, condition=condition,
                            status=node.status, weight=str(node.weight)
                        )
                    )

            return_data.options = LBOptions(timeout=timeout_ms,
                                            retries=retries)

            counter = session.query(Counters).\
                filter(Counters.name == 'api_loadbalancers_create').first()
            counter.value += 1
            session.commit()
            # trigger gearman client to create new lb
            submit_job(
                'UPDATE', device.name, device.id, lb.id
            )

            return return_data