Exemplo n.º 1
0
    def post(self, body=None):
        if self.lbid is None:
            raise ClientSideError('Load Balancer ID has not been supplied')

        tenant_id = get_limited_to_project(request.headers)
        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 get logs from a Load Balancer in a non-ACTIVE '
                    'state, current state: {0}'.format(load_balancer.status)
                )

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

            session.commit()
            data = {
                'deviceid': device.id
            }
            if body.objectStoreType != Unset:
                data['objectStoreType'] = body.objectStoreType.lower()
            else:
                data['objectStoreType'] = 'swift'

            if body.objectStoreBasePath != Unset:
                data['objectStoreBasePath'] = body.objectStoreBasePath
            else:
                data['objectStoreBasePath'] = conf.swift.swift_basepath

            if body.objectStoreEndpoint != Unset:
                data['objectStoreEndpoint'] = body.objectStoreEndpoint
            else:
                data['objectStoreEndpoint'] = '{0}/{1}'.\
                    format(conf.swift.swift_endpoint.rstrip('/'), tenant_id)

            if body.authToken != Unset:
                data['authToken'] = body.authToken
            else:
                data['authToken'] = request.headers.get('X-Auth-Token')

            submit_job(
                'ARCHIVE', device.name, data, self.lbid
            )
            return
Exemplo n.º 2
0
    def delete(self):
        """Remove a load balancer from the account.

        :param load_balancer_id: id of lb

        Urls:
           DELETE   /loadbalancers/{load_balancer_id}

        Notes:
           curl -i -H "Accept: application/json" -X DELETE
           http://dev.server:8080/loadbalancers/1

        Returns: None
        """
        load_balancer_id = self.lbid
        tenant_id = get_limited_to_project(request.headers)
        # grab the lb
        with db_session() as session:
            lb = session.query(LoadBalancer).\
                filter(LoadBalancer.id == load_balancer_id).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').first()

            if lb is None:
                session.rollback()
                raise NotFound("Load Balancer ID is not valid")
            # So we can delete ERROR, but not other Immutable states
            if lb.status in ImmutableStatesNoError:
                session.rollback()
                raise ImmutableEntity(
                    'Cannot delete a Load Balancer in a non-ACTIVE state'
                    ', current state: {0}'.format(lb.status))
            lb.status = 'PENDING_DELETE'
            device = session.query(
                Device.id, Device.name
            ).join(LoadBalancer.devices).\
                filter(LoadBalancer.id == load_balancer_id).\
                first()
            counter = session.query(Counters).\
                filter(Counters.name == 'api_loadbalancers_delete').first()
            counter.value += 1

            if device is None:
                # This can happen if a device was manually deleted from the DB
                lb.status = 'DELETED'
                session.execute(loadbalancers_devices.delete().where(
                    loadbalancers_devices.c.loadbalancer == lb.id))
                session.query(Node).\
                    filter(Node.lbid == lb.id).delete()
                session.query(HealthMonitor).\
                    filter(HealthMonitor.lbid == lb.id).delete()
                session.commit()
            else:
                session.commit()
                submit_job('DELETE', device.name, device.id, lb.id)

            return None
Exemplo n.º 3
0
    def put(self, body=None):
        if not self.lbid:
            raise ClientSideError('Load Balancer ID is required')

        tenant_id = get_limited_to_project(request.headers)
        with db_session() as session:
            # grab the lb
            lb = session.query(LoadBalancer).\
                filter(LoadBalancer.id == self.lbid).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').first()

            if lb is None:
                session.rollback()
                raise NotFound('Load Balancer ID is not valid')

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

            if body.name != Unset:
                namelimit = session.query(Limits.value).\
                    filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
                if len(body.name) > namelimit:
                    session.rollback()
                    raise ClientSideError(
                        'Length of Load Balancer name too long')
                lb.name = body.name

            if body.algorithm != Unset:
                lb.algorithm = body.algorithm

            lb.status = 'PENDING_UPDATE'
            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, lb.id)
            return ''
Exemplo n.º 4
0
    def put(self, body=None):
        """Update the settings for a health monitor.

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

        Url:
           PUT /loadbalancers/{load_balancer_id}/healthmonitor

        Returns: dict
        """
        if not self.lbid:
            raise ClientSideError('Load Balancer ID has not been supplied')

        tenant_id = get_limited_to_project(request.headers)
        with db_session() as session:
            # grab the lb
            query = session.query(LoadBalancer, HealthMonitor).\
                outerjoin(LoadBalancer.monitors).\
                filter(LoadBalancer.id == self.lbid).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').first()

            if query is None:
                session.rollback()
                raise NotFound("Load Balancer not found")

            lb, monitor = query

            if lb is None:
                session.rollback()
                raise NotFound('Load Balancer not found')

            # Check inputs
            if (body.type == Unset or body.type is None or body.delay == Unset
                    or body.delay is None or body.timeout == Unset
                    or body.timeout is None
                    or body.attemptsBeforeDeactivation == Unset
                    or body.attemptsBeforeDeactivation is None):
                session.rollback()
                raise ClientSideError(
                    "Missing field(s): {0}, {1}, {2}, and {3} are required".
                    format("type", "delay", "timeout",
                           "attemptsBeforeDeactivation"))

            data = {
                "lbid": self.lbid,
                "type": body.type,
                "delay": int(body.delay),
                "timeout": int(body.timeout),
                "attempts": int(body.attemptsBeforeDeactivation)
            }

            # Path only required when type is not CONNECT
            if body.path != Unset and body.path is not None:
                if body.type == "CONNECT":
                    session.rollback()
                    raise ClientSideError(
                        "Path argument is invalid with CONNECT type")
                # Encode everything apart from allowed characters
                # This ignore list in the second parameter is everything in
                # RFC3986 section 2 that isn't already ignored by
                # urllib.quote()
                data["path"] = quote(body.path, "/~+*,;:!$'[]()?&=#%")
                # If path is empty, set to /
                if len(data["path"]) == 0 or data["path"][0] != "/":
                    session.rollback()
                    raise ClientSideError("Path must begin with leading /")

                if len(data["path"]) > self.PATH_LIMIT:
                    raise ClientSideError(
                        "Path must be less than {0} characters".format(
                            self.PATH_LIMIT))
            else:
                if body.type != "CONNECT":
                    session.rollback()
                    raise ClientSideError("Path argument is required")
                data["path"] = None

            # Check timeout limits. Must be > 0 and limited to 1 hour
            if data["timeout"] < 1 or data["timeout"] > self.TIMEOUT_LIMIT:
                session.rollback()
                raise ClientSideError(
                    "timeout must be between 1 and {0} seconds".format(
                        self.TIMEOUT_LIMIT))

            # Check delay limits. Must be > 0 and limited to 1 hour
            if data["delay"] < 1 or data["delay"] > self.DELAY_LIMIT:
                session.rollback()
                raise ClientSideError(
                    "delay must be between 1 and {0} seconds".format(
                        self.DELAY_LIMIT))

            if data["timeout"] > data["delay"]:
                session.rollback()
                raise ClientSideError("timeout cannot be greater than delay")

            if (data["attempts"] < 1 or data["attempts"] > 10):
                session.rollback()
                raise ClientSideError(
                    "attemptsBeforeDeactivation must be between 1 and 10")

            if monitor is None:
                # This is ok for LBs that already existed without
                # monitoring. Create a new entry.
                monitor = HealthMonitor(lbid=self.lbid,
                                        type=data["type"],
                                        delay=data["delay"],
                                        timeout=data["timeout"],
                                        attempts=data["attempts"],
                                        path=data["path"])
                session.add(monitor)
            else:
                # Modify the existing entry.
                monitor.type = data["type"]
                monitor.delay = data["delay"]
                monitor.timeout = data["timeout"]
                monitor.attempts = data["attempts"]
                monitor.path = data["path"]

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

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

            return_data = LBMonitorResp()
            return_data.type = data["type"]
            return_data.delay = str(data["delay"])
            return_data.timeout = str(data["timeout"])
            return_data.attemptsBeforeDeactivation =\
                str(data["attempts"])
            if ((data["path"] is not None) and (len(data["path"]) > 0)):
                return_data.path = data["path"]

            counter = session.query(Counters).\
                filter(Counters.name == 'api_healthmonitor_modify').first()
            counter.value += 1
            session.commit()
            submit_job('UPDATE', device.name, device.id, lb.id)
            return return_data
Exemplo n.º 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
        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
Exemplo n.º 6
0
    def put(self, body=None):
        if not self.lbid:
            raise ClientSideError('Load Balancer ID is required')

        tenant_id = get_limited_to_project(request.headers)
        with db_session() as session:
            # grab the lb
            lb = session.query(LoadBalancer).\
                filter(LoadBalancer.id == self.lbid).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').first()

            if lb is None:
                session.rollback()
                raise NotFound('Load Balancer ID is not valid')

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

            if body.name != Unset:
                namelimit = session.query(Limits.value).\
                    filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
                if len(body.name) > namelimit:
                    session.rollback()
                    raise ClientSideError(
                        'Length of Load Balancer name too long')
                lb.name = body.name

            if body.algorithm != Unset:
                lb.algorithm = body.algorithm

            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))
                        lb.timeout = timeout_ms
                    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))
                        lb.retries = retries
                    except ValueError:
                        raise ClientSideError('retries must be an integer')

            lb.status = 'PENDING_UPDATE'
            device = session.query(
                Device.id, Device.name, Device.status
            ).join(LoadBalancer.devices).\
                filter(LoadBalancer.id == self.lbid).\
                first()
            counter = session.query(Counters).\
                filter(Counters.name == 'api_loadbalancers_modify').first()
            counter.value += 1
            session.commit()
            submit_job('UPDATE', device.name, device.id, lb.id)
            return ''
Exemplo n.º 7
0
    def delete(self):
        """Remove a node from the load balancer.

        :param load_balancer_id: id of lb
        :param node_id: id of node

        Url:
           DELETE /loadbalancers/{load_balancer_id}/nodes/{node_id}

        Returns: None
        """
        node_id = self.nodeid
        tenant_id = get_limited_to_project(request.headers)
        if self.lbid is None:
            raise ClientSideError('Load Balancer ID has not been supplied')

        tenant_id = get_limited_to_project(request.headers)
        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'

            nodecount = session.query(Node).\
                filter(Node.lbid == self.lbid).\
                filter(Node.enabled == 1).count()
            # Can't delete the last LB
            if nodecount <= 1:
                session.rollback()
                raise ClientSideError(
                    "Cannot delete the last enabled node in a load balancer")

            node = session.query(Node).\
                filter(Node.lbid == self.lbid).\
                filter(Node.id == node_id).\
                first()
            if not node:
                session.rollback()
                raise NotFound("Node not found in supplied Load Balancer")

            # May not delete the primary node of a Galera LB
            if load_balancer.protocol.lower() == 'galera' and node.backup == 0:
                session.rollback()
                raise ClientSideError(
                    "Cannot delete the primary node in a Galera load balancer")

            session.delete(node)
            device = session.query(
                Device.id, Device.name
            ).join(LoadBalancer.devices).\
                filter(LoadBalancer.id == self.lbid).\
                first()
            session.commit()
            submit_job('UPDATE', device.name, device.id, self.lbid)
            return None
Exemplo n.º 8
0
    def put(self, body=None):
        """ Update a node condition: ENABLED or DISABLED """
        if not self.lbid:
            raise ClientSideError('Load Balancer ID has not been supplied')
        if not self.nodeid:
            raise ClientSideError('Node ID has not been supplied')
        if body.condition == Unset and body.weight == Unset:
            raise ClientSideError('Node condition or weight is required')

        tenant_id = get_limited_to_project(request.headers)
        with db_session() as session:
            # grab the lb
            lb = session.query(LoadBalancer).\
                filter(LoadBalancer.id == self.lbid).\
                filter(LoadBalancer.tenantid == tenant_id).\
                filter(LoadBalancer.status != 'DELETED').first()

            if lb is None:
                session.rollback()
                raise NotFound('Load Balancer ID is not valid')

            node = session.query(Node).\
                filter(Node.lbid == self.lbid).\
                filter(Node.id == self.nodeid).first()

            if node is None:
                session.rollback()
                raise NotFound('Node ID is not valid')

            if body.condition != Unset:
                if body.condition == 'DISABLED':
                    nodecount = session.query(Node).\
                        filter(Node.lbid == self.lbid).\
                        filter(Node.enabled == 1).count()
                    if nodecount <= 1:
                        session.rollback()
                        raise ClientSideError(
                            "Cannot disable the last enabled node")
                    node.enabled = 0
                    node.status = 'OFFLINE'
                else:
                    node.enabled = 1
                    node.status = 'ONLINE'

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

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

            lb.status = 'PENDING_UPDATE'

            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, lb.id)
            return ''
Exemplo n.º 9
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