mirror of
https://github.com/TeaOSLab/EdgeAPI.git
synced 2025-11-06 01:50:25 +08:00
当修改集群主域名和DNS子域名时,自动删除旧的相关域名
This commit is contained in:
@@ -13,6 +13,7 @@ type DNSTaskType = string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DNSTaskTypeClusterChange DNSTaskType = "clusterChange"
|
DNSTaskTypeClusterChange DNSTaskType = "clusterChange"
|
||||||
|
DNSTaskTypeClusterRemoveDomain DNSTaskType = "clusterRemoveDomain" // 从集群中移除域名
|
||||||
DNSTaskTypeNodeChange DNSTaskType = "nodeChange"
|
DNSTaskTypeNodeChange DNSTaskType = "nodeChange"
|
||||||
DNSTaskTypeServerChange DNSTaskType = "serverChange"
|
DNSTaskTypeServerChange DNSTaskType = "serverChange"
|
||||||
DNSTaskTypeDomainChange DNSTaskType = "domainChange"
|
DNSTaskTypeDomainChange DNSTaskType = "domainChange"
|
||||||
@@ -40,7 +41,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateDNSTask 生成任务
|
// CreateDNSTask 生成任务
|
||||||
func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int64, nodeId int64, domainId int64, taskType string) error {
|
func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int64, nodeId int64, domainId int64, recordName string, taskType string) error {
|
||||||
if clusterId <= 0 && serverId <= 0 && nodeId <= 0 && domainId <= 0 {
|
if clusterId <= 0 && serverId <= 0 && nodeId <= 0 && domainId <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int6
|
|||||||
"serverId": serverId,
|
"serverId": serverId,
|
||||||
"nodeId": nodeId,
|
"nodeId": nodeId,
|
||||||
"domainId": domainId,
|
"domainId": domainId,
|
||||||
|
"recordName": recordName,
|
||||||
"updatedAt": time.Now().Unix(),
|
"updatedAt": time.Now().Unix(),
|
||||||
"type": taskType,
|
"type": taskType,
|
||||||
"isDone": false,
|
"isDone": false,
|
||||||
@@ -63,24 +65,29 @@ func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int6
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClusterTask 生成集群任务
|
// CreateClusterTask 生成集群变更任务
|
||||||
func (this *DNSTaskDAO) CreateClusterTask(tx *dbs.Tx, clusterId int64, taskType DNSTaskType) error {
|
func (this *DNSTaskDAO) CreateClusterTask(tx *dbs.Tx, clusterId int64, taskType DNSTaskType) error {
|
||||||
return this.CreateDNSTask(tx, clusterId, 0, 0, 0, taskType)
|
return this.CreateDNSTask(tx, clusterId, 0, 0, 0, "", taskType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateClusterRemoveTask 生成集群删除域名任务
|
||||||
|
func (this *DNSTaskDAO) CreateClusterRemoveTask(tx *dbs.Tx, clusterId int64, domainId int64, recordName string) error {
|
||||||
|
return this.CreateDNSTask(tx, clusterId, 0, 0, domainId, recordName, DNSTaskTypeClusterRemoveDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNodeTask 生成节点任务
|
// CreateNodeTask 生成节点任务
|
||||||
func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, nodeId int64, taskType DNSTaskType) error {
|
func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, nodeId int64, taskType DNSTaskType) error {
|
||||||
return this.CreateDNSTask(tx, 0, 0, nodeId, 0, taskType)
|
return this.CreateDNSTask(tx, 0, 0, nodeId, 0, "", taskType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateServerTask 生成服务任务
|
// CreateServerTask 生成服务任务
|
||||||
func (this *DNSTaskDAO) CreateServerTask(tx *dbs.Tx, clusterId int64, serverId int64, taskType DNSTaskType) error {
|
func (this *DNSTaskDAO) CreateServerTask(tx *dbs.Tx, clusterId int64, serverId int64, taskType DNSTaskType) error {
|
||||||
return this.CreateDNSTask(tx, clusterId, serverId, 0, 0, taskType)
|
return this.CreateDNSTask(tx, clusterId, serverId, 0, 0, "", taskType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDomainTask 生成域名更新任务
|
// CreateDomainTask 生成域名更新任务
|
||||||
func (this *DNSTaskDAO) CreateDomainTask(tx *dbs.Tx, domainId int64, taskType DNSTaskType) error {
|
func (this *DNSTaskDAO) CreateDomainTask(tx *dbs.Tx, domainId int64, taskType DNSTaskType) error {
|
||||||
return this.CreateDNSTask(tx, 0, 0, 0, domainId, taskType)
|
return this.CreateDNSTask(tx, 0, 0, 0, domainId, "", taskType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAllDoingTasks 查找所有正在执行的任务
|
// FindAllDoingTasks 查找所有正在执行的任务
|
||||||
@@ -101,6 +108,7 @@ func (this *DNSTaskDAO) FindAllDoingOrErrorTasks(tx *dbs.Tx, nodeClusterId int64
|
|||||||
}
|
}
|
||||||
_, err = query.
|
_, err = query.
|
||||||
Where("(isDone=0 OR (isDone=1 AND isOk=0))").
|
Where("(isDone=0 OR (isDone=1 AND isOk=0))").
|
||||||
|
Asc("updatedAt").
|
||||||
AscPk().
|
AscPk().
|
||||||
Slice(&result).
|
Slice(&result).
|
||||||
FindAll()
|
FindAll()
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
// DNS更新任务
|
// DNSTask DNS更新任务
|
||||||
type DNSTask struct {
|
type DNSTask struct {
|
||||||
Id uint64 `field:"id"` // ID
|
Id uint64 `field:"id"` // ID
|
||||||
ClusterId uint32 `field:"clusterId"` // 集群ID
|
ClusterId uint32 `field:"clusterId"` // 集群ID
|
||||||
ServerId uint32 `field:"serverId"` // 服务ID
|
ServerId uint32 `field:"serverId"` // 服务ID
|
||||||
NodeId uint32 `field:"nodeId"` // 节点ID
|
NodeId uint32 `field:"nodeId"` // 节点ID
|
||||||
DomainId uint32 `field:"domainId"` // 域名ID
|
DomainId uint32 `field:"domainId"` // 域名ID
|
||||||
|
RecordName string `field:"recordName"` // 记录名
|
||||||
Type string `field:"type"` // 任务类型
|
Type string `field:"type"` // 任务类型
|
||||||
UpdatedAt uint64 `field:"updatedAt"` // 更新时间
|
UpdatedAt uint64 `field:"updatedAt"` // 更新时间
|
||||||
IsDone bool `field:"isDone"` // 是否已完成
|
IsDone bool `field:"isDone"` // 是否已完成
|
||||||
@@ -20,6 +21,7 @@ type DNSTaskOperator struct {
|
|||||||
ServerId interface{} // 服务ID
|
ServerId interface{} // 服务ID
|
||||||
NodeId interface{} // 节点ID
|
NodeId interface{} // 节点ID
|
||||||
DomainId interface{} // 域名ID
|
DomainId interface{} // 域名ID
|
||||||
|
RecordName interface{} // 记录名
|
||||||
Type interface{} // 任务类型
|
Type interface{} // 任务类型
|
||||||
UpdatedAt interface{} // 更新时间
|
UpdatedAt interface{} // 更新时间
|
||||||
IsDone interface{} // 是否已完成
|
IsDone interface{} // 是否已完成
|
||||||
|
|||||||
@@ -471,7 +471,29 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
|
|||||||
if clusterId <= 0 {
|
if clusterId <= 0 {
|
||||||
return errors.New("invalid clusterId")
|
return errors.New("invalid clusterId")
|
||||||
}
|
}
|
||||||
op := NewNodeClusterOperator()
|
|
||||||
|
// 删除老的域名中相关记录
|
||||||
|
oldOne, err := this.Query(tx).
|
||||||
|
Pk(clusterId).
|
||||||
|
Result("dnsName", "dnsDomainId").
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oldOne == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldCluster = oldOne.(*NodeCluster)
|
||||||
|
var oldDNSDomainId = int64(oldCluster.DnsDomainId)
|
||||||
|
if (oldDNSDomainId > 0 && oldDNSDomainId != dnsDomainId) || (oldCluster.DnsName != dnsName) {
|
||||||
|
err = dns.SharedDNSTaskDAO.CreateClusterRemoveTask(tx, clusterId, oldDNSDomainId, oldCluster.DnsName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var op = NewNodeClusterOperator()
|
||||||
op.Id = clusterId
|
op.Id = clusterId
|
||||||
op.DnsName = dnsName
|
op.DnsName = dnsName
|
||||||
op.DnsDomainId = dnsDomainId
|
op.DnsDomainId = dnsDomainId
|
||||||
@@ -480,7 +502,7 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
|
|||||||
cnameRecords = []string{}
|
cnameRecords = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsConfig := &dnsconfigs.ClusterDNSConfig{
|
var dnsConfig = &dnsconfigs.ClusterDNSConfig{
|
||||||
NodesAutoSync: nodesAutoSync,
|
NodesAutoSync: nodesAutoSync,
|
||||||
ServersAutoSync: serversAutoSync,
|
ServersAutoSync: serversAutoSync,
|
||||||
CNameRecords: cnameRecords,
|
CNameRecords: cnameRecords,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func (this *DNSTaskService) FindAllDoingDNSTasks(ctx context.Context, req *pb.Fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch task.Type {
|
switch task.Type {
|
||||||
case dns.DNSTaskTypeClusterChange:
|
case dns.DNSTaskTypeClusterChange, dns.DNSTaskTypeClusterRemoveDomain:
|
||||||
clusterName, err := models.SharedNodeClusterDAO.FindNodeClusterName(tx, int64(task.ClusterId))
|
clusterName, err := models.SharedNodeClusterDAO.FindNodeClusterName(tx, int64(task.ClusterId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ func (this *DNSTaskExecutor) Loop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case dnsmodels.DNSTaskTypeClusterRemoveDomain:
|
||||||
|
err = this.doClusterRemove(taskId, int64(task.ClusterId), int64(task.DomainId), task.RecordName)
|
||||||
|
if err != nil {
|
||||||
|
err = dnsmodels.SharedDNSTaskDAO.UpdateDNSTaskError(nil, taskId, err.Error())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
case dnsmodels.DNSTaskTypeDomainChange:
|
case dnsmodels.DNSTaskTypeDomainChange:
|
||||||
err = this.doDomainWithTask(taskId, int64(task.DomainId))
|
err = this.doDomainWithTask(taskId, int64(task.DomainId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,14 +141,14 @@ func (this *DNSTaskExecutor) doServer(taskId int64, oldClusterId int64, serverId
|
|||||||
var recordType = dnstypes.RecordTypeCNAME
|
var recordType = dnstypes.RecordTypeCNAME
|
||||||
|
|
||||||
// 新的DNS设置
|
// 新的DNS设置
|
||||||
manager, newDomainId, domain, clusterDNSName, dnsConfig, err := this.findDNSManager(tx, int64(serverDNS.ClusterId))
|
manager, newDomainId, domain, clusterDNSName, dnsConfig, err := this.findDNSManagerWithClusterId(tx, int64(serverDNS.ClusterId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果集群发生了变化,则从老的集群中删除
|
// 如果集群发生了变化,则从老的集群中删除
|
||||||
if oldClusterId > 0 && int64(serverDNS.ClusterId) != oldClusterId {
|
if oldClusterId > 0 && int64(serverDNS.ClusterId) != oldClusterId {
|
||||||
oldManager, oldDomainId, oldDomain, _, _, err := this.findDNSManager(tx, oldClusterId)
|
oldManager, oldDomainId, oldDomain, _, _, err := this.findDNSManagerWithClusterId(tx, oldClusterId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -312,7 +320,7 @@ func (this *DNSTaskExecutor) doCluster(taskId int64, clusterId int64) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var tx *dbs.Tx
|
var tx *dbs.Tx
|
||||||
manager, domainId, domain, clusterDNSName, dnsConfig, err := this.findDNSManager(tx, clusterId)
|
manager, domainId, domain, clusterDNSName, dnsConfig, err := this.findDNSManagerWithClusterId(tx, clusterId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -505,6 +513,83 @@ func (this *DNSTaskExecutor) doCluster(taskId int64, clusterId int64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *DNSTaskExecutor) doClusterRemove(taskId int64, clusterId int64, domainId int64, dnsName string) error {
|
||||||
|
var isOk = false
|
||||||
|
defer func() {
|
||||||
|
if isOk {
|
||||||
|
err := dnsmodels.SharedDNSTaskDAO.UpdateDNSTaskDone(nil, taskId)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("DNSTaskExecutor", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var tx *dbs.Tx
|
||||||
|
if len(dnsName) == 0 {
|
||||||
|
dnsInfo, err := models.SharedNodeClusterDAO.FindClusterDNSInfo(tx, clusterId, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dnsInfo == nil {
|
||||||
|
isOk = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dnsName = dnsInfo.DnsName
|
||||||
|
if len(dnsName) == 0 {
|
||||||
|
isOk = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
domain, manager, err := this.findDNSManagerWithDomainId(tx, domainId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if domain == nil {
|
||||||
|
isOk = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var fullName = dnsName + "." + domain.Name
|
||||||
|
|
||||||
|
records, err := domain.DecodeRecords()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var isChanged bool
|
||||||
|
|
||||||
|
for _, record := range records {
|
||||||
|
// node A
|
||||||
|
if (record.Type == dnstypes.RecordTypeA || record.Type == dnstypes.RecordTypeAAAA) && record.Name == dnsName {
|
||||||
|
err = manager.DeleteRecord(domain.Name, record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
isChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// server CNAME
|
||||||
|
if record.Type == dnstypes.RecordTypeCNAME && strings.TrimRight(record.Value, ".") == fullName {
|
||||||
|
err = manager.DeleteRecord(domain.Name, record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
isChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isChanged {
|
||||||
|
err = dnsmodels.SharedDNSTaskDAO.CreateDomainTask(tx, domainId, dnsmodels.DNSTaskTypeDomainChange)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOk = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (this *DNSTaskExecutor) doDomain(domainId int64) error {
|
func (this *DNSTaskExecutor) doDomain(domainId int64) error {
|
||||||
return this.doDomainWithTask(0, domainId)
|
return this.doDomainWithTask(0, domainId)
|
||||||
}
|
}
|
||||||
@@ -577,7 +662,7 @@ func (this *DNSTaskExecutor) doDomainWithTask(taskId int64, domainId int64) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *DNSTaskExecutor) findDNSManager(tx *dbs.Tx, clusterId int64) (manager dnsclients.ProviderInterface, domainId int64, domain string, clusterDNSName string, dnsConfig *dnsconfigs.ClusterDNSConfig, err error) {
|
func (this *DNSTaskExecutor) findDNSManagerWithClusterId(tx *dbs.Tx, clusterId int64) (manager dnsclients.ProviderInterface, domainId int64, domain string, clusterDNSName string, dnsConfig *dnsconfigs.ClusterDNSConfig, err error) {
|
||||||
clusterDNS, err := models.SharedNodeClusterDAO.FindClusterDNSInfo(tx, clusterId, nil)
|
clusterDNS, err := models.SharedNodeClusterDAO.FindClusterDNSInfo(tx, clusterId, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", "", nil, err
|
return nil, 0, "", "", nil, err
|
||||||
@@ -591,39 +676,51 @@ func (this *DNSTaskExecutor) findDNSManager(tx *dbs.Tx, clusterId int64) (manage
|
|||||||
return nil, 0, "", "", nil, err
|
return nil, 0, "", "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsDomain, err := dnsmodels.SharedDNSDomainDAO.FindEnabledDNSDomain(tx, int64(clusterDNS.DnsDomainId), nil)
|
dnsDomain, manager, err := this.findDNSManagerWithDomainId(tx, int64(clusterDNS.DnsDomainId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", "", nil, err
|
return nil, 0, "", "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dnsDomain == nil {
|
if dnsDomain == nil {
|
||||||
return nil, 0, "", "", nil, nil
|
return nil, 0, "", clusterDNS.DnsName, dnsConfig, nil
|
||||||
}
|
|
||||||
providerId := int64(dnsDomain.ProviderId)
|
|
||||||
if providerId <= 0 {
|
|
||||||
return nil, 0, "", "", nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := dnsmodels.SharedDNSProviderDAO.FindEnabledDNSProvider(tx, providerId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, "", "", nil, err
|
|
||||||
}
|
|
||||||
if provider == nil {
|
|
||||||
return nil, 0, "", "", nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
manager = dnsclients.FindProvider(provider.Type)
|
|
||||||
if manager == nil {
|
|
||||||
remotelogs.Error("DNSTaskExecutor", "unsupported dns provider type '"+provider.Type+"'")
|
|
||||||
return nil, 0, "", "", nil, nil
|
|
||||||
}
|
|
||||||
params, err := provider.DecodeAPIParams()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, "", "", nil, err
|
|
||||||
}
|
|
||||||
err = manager.Auth(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, "", "", nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager, int64(dnsDomain.Id), dnsDomain.Name, clusterDNS.DnsName, dnsConfig, nil
|
return manager, int64(dnsDomain.Id), dnsDomain.Name, clusterDNS.DnsName, dnsConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *DNSTaskExecutor) findDNSManagerWithDomainId(tx *dbs.Tx, domainId int64) (*dnsmodels.DNSDomain, dnsclients.ProviderInterface, error) {
|
||||||
|
dnsDomain, err := dnsmodels.SharedDNSDomainDAO.FindEnabledDNSDomain(tx, domainId, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if dnsDomain == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
providerId := int64(dnsDomain.ProviderId)
|
||||||
|
if providerId <= 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := dnsmodels.SharedDNSProviderDAO.FindEnabledDNSProvider(tx, providerId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if provider == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var manager = dnsclients.FindProvider(provider.Type)
|
||||||
|
if manager == nil {
|
||||||
|
remotelogs.Error("DNSTaskExecutor", "unsupported dns provider type '"+provider.Type+"'")
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
params, err := provider.DecodeAPIParams()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
err = manager.Auth(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return dnsDomain, manager, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user