Files
cloudpods/pkg/cloudproxy/models/forwards.go
Qiu Jian 1d354d8919 1. Remove is_rbac_enable option
2. code changes due to interface change
2021-12-02 10:24:23 +08:00

491 lines
15 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"fmt"
"math/rand"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/sqlchemy"
cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SForward struct {
db.SVirtualResourceBase
ProxyEndpointId string `width:"36" charset:"ascii" nullable:"false" list:"user" update:"user" create:"required"`
ProxyAgentId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"`
Type string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"`
RemoteAddr string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"`
RemotePort int `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"`
BindPortReq int `width:"16" charset:"ascii" nullable:"false" list:"user" update:"user" create:"optional"`
Opaque string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"`
BindPort int `width:"16" charset:"ascii" nullable:"false" list:"user" update:"user" create:"optional"`
LastSeen time.Time `nullable:"true" get:"user" list:"user"`
LastSeenTimeout int `width:"16" charset:"ascii" nullable:"false" list:"user" update:"user" create:"optional" default:"117"`
}
type SForwardManager struct {
db.SVirtualResourceBaseManager
}
var ForwardManager *SForwardManager
func init() {
ForwardManager = &SForwardManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SForward{},
"forwards_tbl",
"forward",
"forwards",
),
}
ForwardManager.SetVirtualObject(ForwardManager)
}
func (man *SForwardManager) validateLocalSetPort(ctx context.Context, data *jsonutils.JSONDict, agentId string, portReq int) (*jsonutils.JSONDict, error) {
var (
fwds []SForward
q = man.Query().
Equals("proxy_agent_id", agentId).
Equals("bind_port_req", portReq)
)
if err := db.FetchModelObjects(man, q, &fwds); err != nil {
return nil, httperrors.NewServerError("query forwards by agent failed: %v", err)
}
if len(fwds) > 0 {
return nil, httperrors.NewConflictError("port %d on agent %s was already occupied",
portReq, agentId)
}
data.Set("bind_port", jsonutils.NewInt(int64(portReq)))
return data, nil
}
func (man *SForwardManager) validateRemoteSetPort(ctx context.Context, data *jsonutils.JSONDict, epId string, portReq int) (*jsonutils.JSONDict, error) {
var (
fwds []SForward
q = man.Query().
Equals("proxy_endpoint_id", epId).
Equals("bind_port_req", portReq)
)
if err := db.FetchModelObjects(man, q, &fwds); err != nil {
return nil, httperrors.NewServerError("query forwards by endpoint id failed: %v", err)
}
if len(fwds) > 0 {
return nil, httperrors.NewConflictError("port %d on proxy endpoint %s was already occupied",
portReq, epId)
}
data.Set("bind_port", jsonutils.NewInt(int64(portReq)))
return data, nil
}
func (man *SForwardManager) validateLocalSelectAgent(ctx context.Context, data *jsonutils.JSONDict, portReq int) (*jsonutils.JSONDict, error) {
agents, err := ProxyAgentManager.allAgents(ctx)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
agentsNum := len(agents)
if agentsNum == 0 {
return nil, httperrors.NewResourceNotFoundError("empty proxy agents set")
}
s := rand.Intn(agentsNum)
for i := s; ; {
agent := &agents[i]
var err error
data, err = man.validateLocalSetPort(ctx, data, agent.Id, portReq)
if err == nil {
data.Set("proxy_agent_id", jsonutils.NewString(agent.Id))
return data, nil
}
i += 1
if i == agentsNum {
i = 0
}
if i == s {
break
}
}
return nil, httperrors.NewResourceNotFoundError("no proxy agent accepts request for port %d", portReq)
}
func (man *SForwardManager) validateRemoteSelectAgent(ctx context.Context, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
agents, err := ProxyAgentManager.allAgents(ctx)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
agentsNum := len(agents)
if agentsNum == 0 {
return nil, httperrors.NewResourceNotFoundError("empty proxy agents set")
}
i := rand.Intn(agentsNum)
agent := agents[i]
data.Set("proxy_agent_id", jsonutils.NewString(agent.Id))
return data, nil
}
func (man *SForwardManager) validatePortReq(
ctx context.Context,
typ string, portReq int, agentId, epId string,
data *jsonutils.JSONDict,
) (*jsonutils.JSONDict, error) {
validateOne := func(portReq int) (*jsonutils.JSONDict, error) {
var err error
switch typ {
case cloudproxy_api.FORWARD_TYPE_LOCAL:
if agentId == "" {
data, err = man.validateLocalSelectAgent(ctx, data, portReq)
} else {
data, err = man.validateLocalSetPort(ctx, data, agentId, portReq)
}
case cloudproxy_api.FORWARD_TYPE_REMOTE:
data, err = man.validateRemoteSetPort(ctx, data, epId, portReq)
}
return data, err
}
if typ == cloudproxy_api.FORWARD_TYPE_REMOTE && agentId == "" {
var err error
data, err = man.validateRemoteSelectAgent(ctx, data)
if err != nil {
return nil, httperrors.NewResourceNotFoundError("select proxy agent: %v", err)
}
}
var err error
if portReq <= 0 {
portTotal := cloudproxy_api.BindPortMax - cloudproxy_api.BindPortMin + 1
portReqStart := rand.Intn(portTotal)
for portInc := portReqStart; ; {
data, err = validateOne(cloudproxy_api.BindPortMin + portInc)
if err == nil {
break
}
portInc += 1
if portInc == portTotal {
portInc = 0
}
if portInc == portReqStart {
return nil, httperrors.NewOutOfResourceError("no available port for bind")
}
}
} else {
data, err = validateOne(portReq)
}
return data, err
}
func (man *SForwardManager) PerformCreateFromServer(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *cloudproxy_api.ForwardCreateFromServerInput) (jsonutils.JSONObject, error) {
data := jsonutils.Marshal(input).(*jsonutils.JSONDict)
typeV := validators.NewStringChoicesValidator("type", cloudproxy_api.FORWARD_TYPES)
portReqV := validators.NewRangeValidator("bind_port_req", cloudproxy_api.BindPortMin, cloudproxy_api.BindPortMax)
remotePortV := validators.NewPortValidator("remote_port")
{
for _, v := range []validators.IValidator{
typeV,
portReqV.Optional(true),
remotePortV,
validators.NewNonNegativeValidator("last_seen_timeout").Optional(true),
} {
if err := v.Validate(data); err != nil {
return nil, err
}
}
}
serverId := input.ServerId
if serverId == "" {
return nil, httperrors.NewBadRequestError("server_id is required")
}
serverInfo, err := getServerInfo(ctx, userCred, serverId)
if err != nil {
return nil, err
}
nic := serverInfo.GetNic()
if nic == nil {
return nil, httperrors.NewBadRequestError("cannot find network interface for this server")
}
proxymatch := ProxyMatchManager.findMatch(ctx, nic.NetworkId, nic.VpcId)
if proxymatch == nil {
return nil, httperrors.NewBadRequestError("cannot find an endpoint for this server")
}
data.Set("opaque", jsonutils.NewString(serverInfo.Server.Id))
data.Set("remote_addr", jsonutils.NewString(nic.IpAddr))
data.Set("proxy_endpoint_id", jsonutils.NewString(proxymatch.ProxyEndpointId))
typ := typeV.Value
agentId := ""
epId := proxymatch.ProxyEndpointId
if data.Contains("bind_port_req") {
portReq := int(portReqV.Value)
data, err = man.validatePortReq(ctx, typ, portReq, agentId, epId, data)
} else {
data, err = man.validatePortReq(ctx, typ, -1, agentId, epId, data)
}
forwardObj, err := db.NewModelObject(man)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
forward := forwardObj.(*SForward)
if err := data.Unmarshal(forward); err != nil {
return nil, httperrors.NewServerError("unmarshal create params: %v", err)
}
forward.Name = fmt.Sprintf("%s-%s-%d", serverInfo.Server.Name, typ, forward.RemotePort)
forward.DomainId = userCred.GetProjectDomainId()
forward.ProjectId = userCred.GetProjectId()
if err := man.TableSpec().Insert(ctx, forward); err != nil {
return nil, httperrors.NewServerError("database insertion error: %v", err)
}
return db.GetItemDetails(man, forward, ctx, userCred)
}
func (man *SForwardManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
endpointV := validators.NewModelIdOrNameValidator("proxy_endpoint", ProxyEndpointManager.Keyword(), ownerId)
agentV := validators.NewModelIdOrNameValidator("proxy_agent", ProxyAgentManager.Keyword(), ownerId)
typeV := validators.NewStringChoicesValidator("type", cloudproxy_api.FORWARD_TYPES)
portReqV := validators.NewRangeValidator("bind_port_req", cloudproxy_api.BindPortMin, cloudproxy_api.BindPortMax)
for _, v := range []validators.IValidator{
endpointV,
agentV.Optional(true),
typeV,
validators.NewIPv4AddrValidator("remote_addr"),
validators.NewPortValidator("remote_port"),
portReqV.Optional(true),
validators.NewNonNegativeValidator("last_seen_timeout").Optional(true),
} {
if err := v.Validate(data); err != nil {
return nil, err
}
}
typ := typeV.Value
epId := endpointV.Model.GetId()
var agentId string
if agentV.Model != nil {
agentId = agentV.Model.GetId()
}
var err error
if data.Contains("bind_port_req") {
portReq := int(portReqV.Value)
data, err = man.validatePortReq(ctx, typ, portReq, agentId, epId, data)
} else {
data, err = man.validatePortReq(ctx, typ, -1, agentId, epId, data)
}
return data, err
}
func (fwd *SForward) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
endpointV := validators.NewModelIdOrNameValidator("proxy_endpoint", ProxyEndpointManager.Keyword(), userCred)
agentV := validators.NewModelIdOrNameValidator("proxy_agent", ProxyAgentManager.Keyword(), userCred)
portReqV := validators.NewRangeValidator("bind_port_req", cloudproxy_api.BindPortMin, cloudproxy_api.BindPortMax)
for _, v := range []validators.IValidator{
endpointV,
agentV.Optional(true),
validators.NewIPv4AddrValidator("remote_addr"),
validators.NewPortValidator("remote_port"),
portReqV,
validators.NewNonNegativeValidator("last_seen_timeout"),
} {
v.Optional(true)
if err := v.Validate(data); err != nil {
return nil, err
}
}
portReq := int(portReqV.Value)
if portReq != fwd.BindPortReq {
var err error
var agentId string
if agentV.Model == nil {
agentId = fwd.ProxyAgentId
} else {
agentId = agentV.Model.GetId()
}
switch typ := fwd.Type; typ {
case cloudproxy_api.FORWARD_TYPE_LOCAL:
data, err = ForwardManager.validateLocalSetPort(ctx, data, agentId, portReq)
case cloudproxy_api.FORWARD_TYPE_REMOTE:
data, err = ForwardManager.validateRemoteSetPort(ctx, data, agentId, portReq)
}
if err != nil {
return nil, err
}
}
return data, nil
}
func (man *SForwardManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
input cloudproxy_api.ForwardListInput,
) (*sqlchemy.SQuery, error) {
filters := [][2]string{
[2]string{"type", input.Type},
[2]string{"remote_addr", input.RemoteAddr},
[2]string{"proxy_endpoint_id", input.ProxyEndpointId},
[2]string{"proxy_agent_id", input.ProxyAgentId},
[2]string{"opaque", input.Opaque},
}
for _, filter := range filters {
if v := filter[1]; v != "" {
q = q.Equals(filter[0], v)
}
}
intFilters := []struct {
name string
val *int
}{
{"remote_port", input.RemotePort},
{"bind_port_req", input.BindPortReq},
}
for _, filter := range intFilters {
if v := filter.val; v != nil {
q = q.Equals(filter.name, *v)
}
}
return q, nil
}
func (man *SForwardManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []*jsonutils.JSONDict {
fwds := gotypes.ConvertSliceElemType(objs, (**SForward)(nil)).([]*SForward)
paMap := map[string]*SProxyAgent{}
peMap := map[string]*SProxyEndpoint{}
{
var paIds []string
var peIds []string
{
paIdMap := map[string]string{}
peIdMap := map[string]string{}
for _, fwd := range fwds {
paIdMap[fwd.ProxyAgentId] = ""
peIdMap[fwd.ProxyEndpointId] = ""
}
for id := range paIdMap {
if id != "" {
paIds = append(paIds, id)
}
}
for id := range peIdMap {
if id != "" {
peIds = append(peIds, id)
}
}
}
var pas []SProxyAgent
var pes []SProxyEndpoint
{
paQ := ProxyAgentManager.Query().In("id", paIds)
if err := db.FetchModelObjects(ProxyAgentManager, paQ, &pas); err != nil {
return nil
}
peQ := ProxyEndpointManager.Query().In("id", peIds)
if err := db.FetchModelObjects(ProxyEndpointManager, peQ, &pes); err != nil {
return nil
}
}
for i := range pas {
pa := &pas[i]
paMap[pa.Id] = pa
}
for i := range pes {
pe := &pes[i]
peMap[pe.Id] = pe
}
}
r := make([]*jsonutils.JSONDict, len(objs))
for i, fwd := range fwds {
d := jsonutils.NewDict()
pa, paOK := paMap[fwd.ProxyAgentId]
pe, peOK := peMap[fwd.ProxyEndpointId]
if paOK || peOK {
if paOK {
d.Set("proxy_agent", jsonutils.NewString(pa.Name))
}
if peOK {
d.Set("proxy_endpoint", jsonutils.NewString(pe.Name))
}
switch fwd.Type {
case cloudproxy_api.FORWARD_TYPE_LOCAL:
if paOK {
d.Set("bind_addr", jsonutils.NewString(pa.AdvertiseAddr))
}
case cloudproxy_api.FORWARD_TYPE_REMOTE:
if peOK {
d.Set("bind_addr", jsonutils.NewString(pe.IntranetIpAddr))
}
}
r[i] = d
}
}
return r
}
func (fwd *SForward) PerformHeartbeat(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *cloudproxy_api.ForwardHeartbeatInput) (jsonutils.JSONObject, error) {
if _, err := db.Update(fwd, func() error {
fwd.LastSeen = time.Now()
return nil
}); err != nil {
return nil, err
}
return nil, nil
}
func (fwd *SForward) GetDetailsLastseen(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
ret := jsonutils.NewDict()
var lastSeen string
if fwd.LastSeen.IsZero() {
lastSeen = ""
} else {
lastSeen = fwd.LastSeen.Format("2006-01-02T15:04:05.000000Z")
}
ret.Add(jsonutils.NewString(lastSeen), "last_seen")
return ret, nil
}