Merge "[API] Support new GALERA protocol in the API"
This commit is contained in:
@@ -180,6 +180,16 @@ class LoadBalancersController(RestController):
|
||||
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(
|
||||
@@ -194,6 +204,7 @@ class LoadBalancersController(RestController):
|
||||
'Node {0} port number {1} is invalid'
|
||||
.format(node.address, node.port)
|
||||
)
|
||||
|
||||
try:
|
||||
node.address = ipfilter(node.address, conf.ip_filters)
|
||||
except IPOutOfRange:
|
||||
@@ -205,6 +216,19 @@ class LoadBalancersController(RestController):
|
||||
raise ClientSideError(
|
||||
'IP Address {0} not valid'.format(node.address)
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# 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()
|
||||
@@ -241,8 +265,13 @@ class LoadBalancersController(RestController):
|
||||
lb = LoadBalancer()
|
||||
lb.tenantid = tenant_id
|
||||
lb.name = body.name
|
||||
if body.protocol and body.protocol.lower() == 'tcp':
|
||||
lb.protocol = 'TCP'
|
||||
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'
|
||||
|
||||
@@ -255,8 +284,10 @@ class LoadBalancersController(RestController):
|
||||
else:
|
||||
if lb.protocol == 'HTTP':
|
||||
lb.port = 80
|
||||
else:
|
||||
elif lb.protocol == 'TCP':
|
||||
lb.port = 443
|
||||
elif lb.protocol == 'GALERA':
|
||||
lb.port = 3306
|
||||
|
||||
lb.status = 'BUILD'
|
||||
lb.created = None
|
||||
@@ -351,9 +382,16 @@ class LoadBalancersController(RestController):
|
||||
else:
|
||||
enabled = 1
|
||||
node_status = 'ONLINE'
|
||||
|
||||
if node.backup == 'TRUE':
|
||||
backup = 1
|
||||
else:
|
||||
backup = 0
|
||||
|
||||
out_node = Node(
|
||||
lbid=lb.id, port=node.port, address=node.address,
|
||||
enabled=enabled, status=node_status, weight=1
|
||||
enabled=enabled, status=node_status, weight=1,
|
||||
backup=backup
|
||||
)
|
||||
session.add(out_node)
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ class NodesController(RestController):
|
||||
raise ClientSideError(
|
||||
'IP Address {0} not valid'.format(node.address)
|
||||
)
|
||||
|
||||
with db_session() as session:
|
||||
load_balancer = session.query(LoadBalancer).\
|
||||
filter(LoadBalancer.tenantid == tenant_id).\
|
||||
@@ -157,20 +158,36 @@ class NodesController(RestController):
|
||||
raise NotFound('Load Balancer not found')
|
||||
|
||||
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'
|
||||
@@ -179,7 +196,8 @@ class NodesController(RestController):
|
||||
node_status = 'ONLINE'
|
||||
new_node = Node(
|
||||
lbid=self.lbid, port=node.port, address=node.address,
|
||||
enabled=enabled, status=node_status, weight=1
|
||||
enabled=enabled, status=node_status,
|
||||
weight=1, backup=int(is_backup)
|
||||
)
|
||||
session.add(new_node)
|
||||
session.flush()
|
||||
@@ -194,6 +212,7 @@ class NodesController(RestController):
|
||||
status=new_node.status
|
||||
)
|
||||
)
|
||||
|
||||
device = session.query(
|
||||
Device.id, Device.name, Device.status
|
||||
).join(LoadBalancer.devices).\
|
||||
@@ -214,6 +233,7 @@ class NodesController(RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(None, body=LBNodePut, status_code=202)
|
||||
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:
|
||||
@@ -301,7 +321,9 @@ class NodesController(RestController):
|
||||
if load_balancer is None:
|
||||
session.rollback()
|
||||
raise NotFound("Load Balancer not found")
|
||||
|
||||
load_balancer.status = 'PENDING_UPDATE'
|
||||
|
||||
nodecount = session.query(Node).\
|
||||
filter(Node.lbid == self.lbid).\
|
||||
filter(Node.enabled == 1).count()
|
||||
@@ -311,6 +333,7 @@ class NodesController(RestController):
|
||||
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).\
|
||||
@@ -320,6 +343,14 @@ class NodesController(RestController):
|
||||
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
|
||||
|
||||
@@ -46,6 +46,10 @@ class Responses(object):
|
||||
{
|
||||
'name': 'TCP',
|
||||
'port': '443'
|
||||
},
|
||||
{
|
||||
'name': 'GALERA',
|
||||
'port': '3306'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class LBNode(Base):
|
||||
port = wsattr(int, mandatory=True)
|
||||
address = wsattr(wtypes.text, mandatory=True)
|
||||
condition = Enum(wtypes.text, 'ENABLED', 'DISABLED')
|
||||
backup = Enum(wtypes.text, 'TRUE', 'FALSE')
|
||||
|
||||
|
||||
class LBRespNode(Base):
|
||||
|
||||
@@ -272,11 +272,15 @@ class GearmanClientThread(object):
|
||||
if not node.enabled:
|
||||
continue
|
||||
condition = 'ENABLED'
|
||||
backup = 'FALSE'
|
||||
if node.backup != 0:
|
||||
backup = 'TRUE'
|
||||
node_data = {
|
||||
'id': node.id, 'port': node.port,
|
||||
'address': node.address, 'weight': node.weight,
|
||||
'condition': condition
|
||||
'condition': condition, 'backup': backup
|
||||
}
|
||||
|
||||
lb_data['nodes'].append(node_data)
|
||||
# Track if we have a DEGRADED LB
|
||||
if node.status == 'ERROR':
|
||||
|
||||
@@ -117,7 +117,7 @@ class Node(DeclarativeBase):
|
||||
__tablename__ = 'nodes'
|
||||
#column definitions
|
||||
address = Column(u'address', VARCHAR(length=128), nullable=False)
|
||||
enabled = Column(u'enabled', Integer(), nullable=False)
|
||||
enabled = Column(u'enabled', INTEGER(), nullable=False)
|
||||
id = Column(u'id', BIGINT(), primary_key=True, nullable=False)
|
||||
lbid = Column(
|
||||
u'lbid', BIGINT(), ForeignKey('loadbalancers.id'), nullable=False
|
||||
@@ -125,6 +125,7 @@ class Node(DeclarativeBase):
|
||||
port = Column(u'port', INTEGER(), nullable=False)
|
||||
status = Column(u'status', VARCHAR(length=128), nullable=False)
|
||||
weight = Column(u'weight', INTEGER(), nullable=False)
|
||||
backup = Column(u'backup', INTEGER(), nullable=False, default=0)
|
||||
|
||||
|
||||
class HealthMonitor(DeclarativeBase):
|
||||
|
||||
@@ -38,6 +38,7 @@ CREATE TABLE loadbalancers (
|
||||
weight INT NOT NULL, # Node weight if applicable to algorithm used
|
||||
enabled BOOLEAN NOT NULL, # is node enabled or not
|
||||
status VARCHAR(128) NOT NULL, # status of node 'OFFLINE', 'ONLINE', 'ERROR', this value is reported by the device
|
||||
backup BOOLEAN NOT NULL DEFAULT FALSE, # true if a backup node
|
||||
PRIMARY KEY (id) # ids are unique accross all Nodes
|
||||
) DEFAULT CHARSET utf8 DEFAULT COLLATE utf8_general_ci;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user