// 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" "strconv" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/sqlchemy" cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy" compute_apis "yunion.io/x/onecloud/pkg/apis/compute" "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" ) // Add revision? type SProxyEndpoint struct { db.SVirtualResourceBase User string `nullable:"false" list:"user" update:"user" create:"optional"` Host string `nullable:"false" list:"user" update:"user" create:"required"` Port int `nullable:"false" list:"user" update:"user" create:"optional"` PrivateKey string `nullable:"false" update:"user" list:"admin" get:"admin" create:"required"` // do not allow get, list IntranetIpAddr string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"required"` StatusDetail string `width:"128" charset:"ascii" nullable:"false" default:"init" list:"user" create:"optional" json:"status_detail"` } type SProxyEndpointManager struct { db.SVirtualResourceBaseManager } var ProxyEndpointManager *SProxyEndpointManager func init() { ProxyEndpointManager = &SProxyEndpointManager{ SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( SProxyEndpoint{}, "proxy_endpoints_tbl", "proxy_endpoint", "proxy_endpoints", ), } ProxyEndpointManager.SetVirtualObject(ProxyEndpointManager) } func (man *SProxyEndpointManager) PerformCreateFromServer(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *cloudproxy_api.ProxyEndpointCreateFromServerInput) (jsonutils.JSONObject, error) { 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 } if serverInfo.PrivateKey == "" { return nil, httperrors.NewBadRequestError("cannot find ssh private key for this server") } nic := serverInfo.GetNic() if nic == nil { return nil, httperrors.NewBadRequestError("cannot find usable network interface for this server") } host := serverInfo.Server.Eip if host == "" && nic.VpcId == compute_apis.DEFAULT_VPC_ID { host = nic.IpAddr } if host == "" { return nil, httperrors.NewBadRequestError("cannot find ssh host ip address for this server") } var name string if len(input.Name) > 0 { name = input.Name } else if len(input.GenerateName) > 0 { name, err = db.GenerateName(ctx, man, userCred, input.GenerateName) if err != nil { return nil, errors.Wrapf(err, "db.GenerateName") } } else { name, err = db.GenerateName(ctx, man, userCred, serverInfo.Server.Name) if err != nil { return nil, errors.Wrapf(err, "db.GenerateName") } } if err := db.NewNameValidator(ctx, man, userCred, name, nil); err != nil { return nil, httperrors.NewGeneralError(err) } port := 22 if portStr, ok := serverInfo.Server.Metadata[compute_apis.SSH_PORT]; ok { port, _ = strconv.Atoi(portStr) } proxyendpoint := &SProxyEndpoint{ User: "cloudroot", Host: host, Port: port, PrivateKey: serverInfo.PrivateKey, IntranetIpAddr: nic.IpAddr, } proxyendpoint.SetModelManager(man, proxyendpoint) proxyendpoint.Name = name proxyendpoint.DomainId = serverInfo.Server.DomainId proxyendpoint.ProjectId = serverInfo.Server.ProjectId if err := proxyendpoint.remoteCheckMake(ctx, userCred); err != nil { return nil, err } if err := man.TableSpec().Insert(ctx, proxyendpoint); err != nil { return nil, httperrors.NewServerError("database insertion error: %v", err) } var proxymatches []*SProxyMatch if nic.VpcId != "" { pm := &SProxyMatch{ ProxyEndpointId: proxyendpoint.Id, MatchScope: cloudproxy_api.PM_SCOPE_VPC, MatchValue: nic.VpcId, } pm.Name = "vpc-" + nic.VpcId proxymatches = append(proxymatches, pm) } if nic.NetworkId != "" { pm := &SProxyMatch{ ProxyEndpointId: proxyendpoint.Id, MatchScope: cloudproxy_api.PM_SCOPE_NETWORK, MatchValue: nic.NetworkId, } pm.Name = "network-" + nic.NetworkId proxymatches = append(proxymatches, pm) } for _, proxymatch := range proxymatches { proxymatch.DomainId = userCred.GetProjectDomainId() proxymatch.ProjectId = userCred.GetProjectId() if err := ProxyMatchManager.TableSpec().Insert(ctx, proxymatch); err != nil { log.Errorf("failed insertion of proxy match %s: %v", proxymatch.Name, err) } } return jsonutils.Marshal(proxyendpoint), nil } func (man *SProxyEndpointManager) ValidateCreateData( ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input cloudproxy_api.ProxyEndpointCreateInput, ) (*jsonutils.JSONDict, error) { data := jsonutils.Marshal(input).(*jsonutils.JSONDict) if input, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput); err != nil { return nil, err } else { data.Update(jsonutils.Marshal(input)) } vs := []validators.IValidator{ validators.NewStringNonEmptyValidator("user").Default("cloudroot"), validators.NewStringNonEmptyValidator("host"), validators.NewPortValidator("port").Default(22), validators.NewSSHKeyValidator("private_key").Optional(true), validators.NewIPv4AddrValidator("intranet_ip_addr"), } for _, v := range vs { if err := v.Validate(ctx, data); err != nil { return nil, err } } // populate ssh credential through "cloudhost" // // if ! skip validation { // ssh credential validation // } return data, nil } func (proxyendpoint *SProxyEndpoint) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { err := proxyendpoint.remoteCheckMake(ctx, userCred) return err } func (man *SProxyEndpointManager) getById(id string) (*SProxyEndpoint, error) { m, err := db.FetchById(man, id) if err != nil { return nil, err } proxyendpoint := m.(*SProxyEndpoint) return proxyendpoint, err } func (proxyendpoint *SProxyEndpoint) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input cloudproxy_api.ProxyEndpointUpdateInput) (cloudproxy_api.ProxyEndpointUpdateInput, error) { var err error input.VirtualResourceBaseUpdateInput, err = proxyendpoint.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.VirtualResourceBaseUpdateInput) if err != nil { return input, errors.Wrap(err, "SVirtualResourceBase.ValidateUpdateData") } data := jsonutils.Marshal(input).(*jsonutils.JSONDict) vs := []validators.IValidator{ validators.NewStringNonEmptyValidator("user"), validators.NewStringNonEmptyValidator("host"), validators.NewPortValidator("port"), validators.NewSSHKeyValidator("private_key").Optional(true), } for _, v := range vs { v.Optional(true) if err := v.Validate(ctx, data); err != nil { return input, err } } return input, nil } func (proxyendpoint *SProxyEndpoint) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error { q := ForwardManager.Query().Equals("proxy_endpoint_id", proxyendpoint.Id) if count, err := q.CountWithError(); err != nil { return httperrors.NewServerError("count forwards using proxy endpoint %s(%s)", proxyendpoint.Name, proxyendpoint.Id) } else if count > 0 { return httperrors.NewConflictError("proxy endpoint %s(%s) is still used by %d forward(s)", proxyendpoint.Name, proxyendpoint.Id, count) } else { return nil } } func (proxyendpoint *SProxyEndpoint) PerformPurgeForwards(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { q := ForwardManager.Query().Equals("proxy_endpoint_id", proxyendpoint.Id) forwards := make([]SForward, 0) err := db.FetchModelObjects(ForwardManager, q, &forwards) if err != nil { return nil, errors.Wrapf(err, "unable to fetch all forwards for proxyendpoint %s", proxyendpoint.Id) } for i := range forwards { err := forwards[i].Delete(ctx, userCred) if err != nil { return nil, errors.Wrapf(err, "unable to delete forward %s", forwards[i].Id) } } return nil, nil } func (proxyendpoint *SProxyEndpoint) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { var pms []SProxyMatch q := ProxyMatchManager.Query().Equals("proxy_endpoint_id", proxyendpoint.Id) if err := db.FetchModelObjects(ProxyMatchManager, q, &pms); err != nil { return httperrors.NewServerError("fetch proxy matches for endpoint %s(%s)", proxyendpoint.Name, proxyendpoint.Id) } for i := range pms { pm := &pms[i] err := db.DeleteModel(ctx, userCred, pm) if err != nil { return err } } return nil } func (man *SProxyEndpointManager) ListItemFilter( ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input cloudproxy_api.ProxyEndpointListInput, ) (*sqlchemy.SQuery, error) { q, err := man.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput) if err != nil { return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter") } filters := [][2]string{ [2]string{cloudproxy_api.PM_SCOPE_VPC, input.VpcId}, [2]string{cloudproxy_api.PM_SCOPE_NETWORK, input.NetworkId}, } for _, filter := range filters { if v := filter[1]; v != "" { pmQ := ProxyMatchManager.Query("proxy_endpoint_id"). Equals("match_scope", filter[0]). Equals("match_value", v) q = q.In("id", pmQ.SubQuery()) } } return q, nil }