diff --git a/cli/ctl/domain_additional_resource.go b/cli/ctl/domain_additional_resource.go index ee7d6361056..0689504f0e3 100644 --- a/cli/ctl/domain_additional_resource.go +++ b/cli/ctl/domain_additional_resource.go @@ -18,6 +18,7 @@ package ctl import ( "fmt" + "html/template" "io/ioutil" "os" @@ -57,7 +58,24 @@ func RegisterDomainAdditionalResourceCommand() *cobra.Command { }, } + var resourceType, resourceName string + list := &cobra.Command{ + Use: "list", + Short: "list domain additional resource", + Example: "deepflow-ctl domain additional-resource list", + Run: func(cmd *cobra.Command, args []string) { + if resourceName != "" && resourceType == "" { + fmt.Printf("please enter resource type, resource name(%v)\n", resourceName) + return + } + listDomainAdditionalResource(cmd, resourceType, resourceName) + }, + } + list.Flags().StringVarP(&resourceType, "type", "", "", "resource type, support: az, vpc, subnet, host, chonst, lb") + list.Flags().StringVarP(&resourceName, "name", "", "", "resource name") + DomainAdditionalResource.AddCommand(apply) + DomainAdditionalResource.AddCommand(list) DomainAdditionalResource.AddCommand(exampleCmd) return DomainAdditionalResource } @@ -77,6 +95,131 @@ func applyDomainAdditionalResource(cmd *cobra.Command, args []string, filename s } } +var additionalListTemplate = `{{- if .AZS }} +azs:{{ range .AZS }} +- name: {{ .NAME }} + uuid: {{ .UUID }} + domain_uuid: {{ .DOMAIN_UUID }} +{{- end }}{{ end }} + +{{- if .VPCS }} +vpcs:{{ range .VPCS }} +- name: {{ .NAME }} + uuid: {{ .UUID }} + domain_uuid: {{ .DOMAIN_UUID }} +{{- end }}{{ end }} + +{{- if .SUBNETS }} +subnets:{{ range .SUBNETS }} +- name: {{ .NAME }} + uuid: {{ .UUID }} + type: {{ .TYPE }} + is_vip: {{ .IS_VIP }} + vpc_uuid: {{ .VPC_UUID }} + {{- if .AZ_UUID }}az_uuid: {{ .AZ_UUID }}{{ end }} + domain_uuid: {{ .DOMAIN_UUID }} + cidrs:{{ range .CIDRS }} + - {{ . }} + {{- end }} +{{- end }}{{ end }} + +{{- if .HOSTS }} +hosts:{{ range .HOSTS }} +- name: {{ .NAME }} + uuid: {{ .UUID }} + ip: {{ .IP }} + type: {{ .TYPE }} + az_uuid: {{ .AZ_UUID }} + domain_uuid: {{ .DOMAIN_UUID }} + {{- if .VINTERFACES }} + vinterfaces: {{ range .VINTERFACES }} + - mac: {{ .MAC }} + name: {{ .NAME }} + subnet_uuid: {{ .SUBNET_UUID }} + {{- if .IPS }} + ips:{{ range .IPS }} + - {{ . }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }}{{- end }} + +{{- if .CHOSTS }} +chosts:{{ range .CHOSTS }} +- name: {{ .NAME }} + uuid: {{ .UUID }} + host_ip: {{ .HOST_IP }} + type: {{ .TYPE }} + vpc_uuid: {{ .VPC_UUID }} + az_uuid: {{ .AZ_UUID }} + domain_uuid: {{ .DOMAIN_UUID }} + {{- if .VINTERFACES }} + vinterfaces: {{ range .VINTERFACES }} + - mac: {{ .MAC }} + subnet_uuid: {{ .SUBNET_UUID }} + {{- if .IPS }} + ips:{{ range .IPS }} + - {{ . }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }}{{- end }} + +{{- if .LBS }} +lbs:{{ range .LBS }} +- name: {{ .NAME }} + model: {{ .MODEL }} + vpc_uuid: {{ .VPC_UUID }} + domain_uuid: {{ .DOMAIN_UUID }} + region_uuid: {{ .REGION_UUID }} + {{- if .VINTERFACES }} + vinterfaces: {{ range .VINTERFACES }} + - mac: {{ .MAC }} + subnet_uuid: {{ .SUBNET_UUID }} + {{- if .IPS }} + ips:{{ range .IPS}} + - {{ . }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .LB_LISTENERS }} + lb_listeners:{{ range .LB_LISTENERS }} + - {{ if .NAME }}name: {{ .NAME }}{{ end }} + protocol: {{ .PROTOCOL }} + ip: {{ .IP }} + port: {{ .PORT }} + {{ if .LB_TARGET_SERVERS }}lb_target_servers:{{range .LB_TARGET_SERVERS }} + - ip: {{ .IP }} + port: {{ .PORT }} + {{ end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }}{{ end }} +` + +func listDomainAdditionalResource(cmd *cobra.Command, resourceType, resourceName string) { + server := common.GetServerInfo(cmd) + url := fmt.Sprintf("http://%s:%d/v1/domain-additional-resources/?type=%s&name=%s", server.IP, server.Port, resourceType, resourceName) + response, err := common.CURLPerform("GET", url, nil, "", []common.HTTPOption{common.WithTimeout(common.GetTimeout(cmd))}...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + + data, err := response.Get("DATA").Map() + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + + t := template.Must(template.New("domain_additional_list").Parse(additionalListTemplate)) + t.Execute(os.Stdout, data) +} + func exampleDomainAdditionalResourceConfig(cmd *cobra.Command) { fmt.Printf(string(example.YamlDomainAdditionalResourceReader)) } diff --git a/server/controller/http/router/resource/domain.go b/server/controller/http/router/resource/domain.go index a517f6673ab..295f0172fdc 100644 --- a/server/controller/http/router/resource/domain.go +++ b/server/controller/http/router/resource/domain.go @@ -56,6 +56,7 @@ func (d *Domain) RegisterTo(e *gin.Engine) { e.DELETE("/v2/sub-domains/:lcuuid/", deleteSubDomain) e.PUT("/v1/domain-additional-resources/", applyDomainAddtionalResource) + e.GET("/v1/domain-additional-resources/", listDomainAddtionalResource) } func getDomain(c *gin.Context) { @@ -242,3 +243,22 @@ func applyDomainAddtionalResource(c *gin.Context) { err = resource.ApplyDomainAddtionalResource(data) common.JsonResponse(c, map[string]interface{}{}, err) } + +func listDomainAddtionalResource(c *gin.Context) { + var resourceType, resourceName string + t, ok := c.GetQuery("type") + if ok { + resourceType = t + } + name, ok := c.GetQuery("name") + if ok { + resourceName = name + } + if resourceName != "" && resourceType == "" { + common.JsonResponse(c, httpcommon.PARAMETER_ILLEGAL, fmt.Errorf("please enter resource type, resource name(%v)", resourceName)) + return + } + + data, err := resource.ListDomainAdditionalResource(resourceType, resourceName) + common.JsonResponse(c, data, err) +} diff --git a/server/controller/http/service/resource/domain_additional_resource.go b/server/controller/http/service/resource/domain_additional_resource.go index 9756b50e9c7..7e850a3429e 100644 --- a/server/controller/http/service/resource/domain_additional_resource.go +++ b/server/controller/http/service/resource/domain_additional_resource.go @@ -506,6 +506,7 @@ func generateCloudModelData(domainUUIDToToolDataSet map[string]*addtionalResourc VPCLcuuid: subnet.VPCUUID, AZLcuuid: subnet.AZUUID, RegionLcuuid: toolDS.regionUUID, + IsVIP: subnet.IsVIP, }, ) for _, cidr := range subnet.CIDRs { diff --git a/server/controller/http/service/resource/domain_additional_resource_list.go b/server/controller/http/service/resource/domain_additional_resource_list.go new file mode 100644 index 00000000000..f8486b2fca1 --- /dev/null +++ b/server/controller/http/service/resource/domain_additional_resource_list.go @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2023 Yunshan Networks + * + * 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 resource + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + "gorm.io/gorm" + + cloudmodel "github.com/deepflowio/deepflow/server/controller/cloud/model" + "github.com/deepflowio/deepflow/server/controller/common" + "github.com/deepflowio/deepflow/server/controller/db/mysql" + "github.com/deepflowio/deepflow/server/controller/model" +) + +func ListDomainAdditionalResource(resourceType, resourceName string) (map[string]interface{}, error) { + domainToResource, err := getResourceFromDB() + if err != nil { + return nil, err + } + + resp := &model.AdditionalResource{} + for domain, resource := range domainToResource { + switch resourceType { + case "az": + resp.AZs = append(resp.AZs, getAZs(resource.AZs, domain, resourceName)...) + case "vpc": + resp.VPCs = append(resp.VPCs, getVPCs(resource.VPCs, domain, resourceName)...) + case "subnet": + resp.Subnets = append(resp.Subnets, getSubnets(resource.Subnets, resource.SubnetCIDRs, domain, resourceName)...) + case "host": + resp.Hosts = append(resp.Hosts, getHosts(resource.Hosts, resource.VInterfaces, resource.IPs, domain, resourceName)...) + case "chost": + resp.CHosts = append(resp.CHosts, getCHosts(resource.CHosts, resource.VInterfaces, resource.IPs, domain, resourceName)...) + case "lb": + resp.LB = append(resp.LB, getLBs(resource.LB, resource.LBListeners, resource.LBTargetServers, + resource.VInterfaces, resource.IPs, domain, resourceName)...) + case "": + resp.AZs = append(resp.AZs, getAZs(resource.AZs, domain, resourceName)...) + resp.VPCs = append(resp.VPCs, getVPCs(resource.VPCs, domain, resourceName)...) + resp.Subnets = append(resp.Subnets, getSubnets(resource.Subnets, resource.SubnetCIDRs, domain, resourceName)...) + resp.Hosts = append(resp.Hosts, getHosts(resource.Hosts, resource.VInterfaces, resource.IPs, domain, resourceName)...) + resp.CHosts = append(resp.CHosts, getCHosts(resource.CHosts, resource.VInterfaces, resource.IPs, domain, resourceName)...) + resp.LB = append(resp.LB, getLBs(resource.LB, resource.LBListeners, resource.LBTargetServers, + resource.VInterfaces, resource.IPs, domain, resourceName)...) + + default: + return nil, fmt.Errorf("resource type(%v) is not supported, please enter: az, vpc, subnet, host, chonst, lb") + } + } + + data := make(map[string]interface{}) + convertToUpperMap(data, reflect.ValueOf(resp).Elem()) + return data, nil +} + +func getAZs(azs []cloudmodel.AZ, domain, resourceName string) []model.AdditionalResourceAZ { + var resp []model.AdditionalResourceAZ + for _, az := range azs { + if resourceName != "" && az.Name != resourceName { + continue + } + resp = append(resp, model.AdditionalResourceAZ{ + Name: az.Name, + UUID: az.Lcuuid, + DomainUUID: domain, + }) + } + return resp +} + +func getVPCs(vpcs []cloudmodel.VPC, domain, resourceName string) []model.AdditionalResourceVPC { + var resp []model.AdditionalResourceVPC + for _, vpc := range vpcs { + if resourceName != "" && vpc.Name != resourceName { + continue + } + resp = append(resp, model.AdditionalResourceVPC{ + Name: vpc.Name, + UUID: vpc.Lcuuid, + DomainUUID: domain, + }) + } + return resp +} + +func getSubnets(subnets []cloudmodel.Network, subnetCIDRs []cloudmodel.Subnet, domain, resourceName string) []model.AdditionalResourceSubnet { + var resp []model.AdditionalResourceSubnet + for _, subnet := range subnets { + if resourceName != "" && subnet.Name != resourceName { + continue + } + subnetAdd := model.AdditionalResourceSubnet{ + DomainUUID: domain, + UUID: subnet.Lcuuid, + Name: subnet.Name, + Type: subnet.NetType, + VPCUUID: subnet.VPCLcuuid, + AZUUID: subnet.AZLcuuid, + IsVIP: subnet.IsVIP, + } + for _, subnetCIDR := range subnetCIDRs { + if subnetCIDR.NetworkLcuuid != subnet.Lcuuid && + subnetCIDR.Lcuuid != common.GenerateUUID(subnet.Lcuuid+subnetCIDR.CIDR) { + continue + } + subnetAdd.CIDRs = append(subnetAdd.CIDRs, subnetCIDR.CIDR) + } + resp = append(resp, subnetAdd) + } + return resp +} + +func getHosts(hosts []cloudmodel.Host, vifs []cloudmodel.VInterface, ips []cloudmodel.IP, domain, resourceName string) []model.AdditionalResourceHost { + var resp []model.AdditionalResourceHost + for _, host := range hosts { + if resourceName != "" && host.Name != resourceName { + continue + } + addHost := model.AdditionalResourceHost{ + DomainUUID: domain, + AZUUID: host.AZLcuuid, + Name: host.Name, + UUID: host.Lcuuid, + IP: host.IP, + Type: host.HType, + } + addHost.VInterfaces = append(addHost.VInterfaces, getVinterfaces(host.Lcuuid, vifs, ips)...) + resp = append(resp, addHost) + } + return resp +} + +func getCHosts(chosts []cloudmodel.VM, vifs []cloudmodel.VInterface, ips []cloudmodel.IP, domain, resourceName string) []model.AdditionalResourceChost { + var resp []model.AdditionalResourceChost + for _, chost := range chosts { + if resourceName != "" && chost.Name != resourceName { + continue + } + addCHost := model.AdditionalResourceChost{ + Name: chost.Name, + UUID: chost.Lcuuid, + HostIP: chost.LaunchServer, + Type: chost.HType, + VPCUUID: chost.VPCLcuuid, + DomainUUID: domain, + AZUUID: chost.AZLcuuid, + } + addCHost.VInterfaces = append(addCHost.VInterfaces, getVinterfaces(chost.Lcuuid, vifs, ips)...) + resp = append(resp, addCHost) + } + return resp +} + +func getLBs(lbs []cloudmodel.LB, lbListeners []cloudmodel.LBListener, lbTargetServers []cloudmodel.LBTargetServer, + vifs []cloudmodel.VInterface, ips []cloudmodel.IP, domain, resourceName string) []model.AdditionalResourceLB { + var resp []model.AdditionalResourceLB + for _, lb := range lbs { + if resourceName != "" && lb.Name != resourceName { + continue + } + lbAdd := model.AdditionalResourceLB{ + Name: lb.Name, + Model: lb.Model, + VPCUUID: lb.VPCLcuuid, + DomainUUID: domain, + RegionUUID: lb.RegionLcuuid, + } + lbAdd.VInterfaces = append(lbAdd.VInterfaces, getVinterfaces(lb.Lcuuid, vifs, ips)...) + for _, lbListener := range lbListeners { + if lbListener.LBLcuuid != lb.Lcuuid { + continue + } + lbListenerAdd := model.AdditionalResourceLBListener{ + Name: lbListener.Name, + Protocol: lbListener.Protocol, + IP: lbListener.IPs, + Port: lbListener.Port, + } + for _, lbTargetServer := range lbTargetServers { + if lbTargetServer.LBLcuuid != lb.Lcuuid && lbTargetServer.LBListenerLcuuid != lbListener.Lcuuid { + continue + } + lbTargetServerAdd := model.AdditionalResourceLBTargetServer{ + IP: lbTargetServer.IP, + Port: lbTargetServer.Port, + } + lbListenerAdd.LBTargetServers = append(lbListenerAdd.LBTargetServers, lbTargetServerAdd) + } + lbAdd.LBListeners = append(lbAdd.LBListeners, lbListenerAdd) + } + + resp = append(resp, lbAdd) + } + return resp +} + +func convertToUpperMap(data map[string]interface{}, v reflect.Value) { + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + + tag := field.Tag.Get("json") + if value.IsZero() && strings.Contains(tag, "omitempty") { + continue + } + tag = strings.Split(tag, ",")[0] + tag = strings.ToUpper(tag) + + switch value.Kind() { + case reflect.Struct: + subData := make(map[string]interface{}) + convertToUpperMap(subData, value) + data[tag] = subData + case reflect.Slice: + sliceData := make([]interface{}, value.Len()) + for j := 0; j < value.Len(); j++ { + subValue := value.Index(j) + if subValue.Kind() == reflect.Struct { + subData := make(map[string]interface{}) + convertToUpperMap(subData, subValue) + sliceData[j] = subData + } else { + sliceData[j] = subValue.Interface() + } + } + data[tag] = sliceData + default: + data[tag] = value.Interface() + } + } +} + +func getResourceFromDB() (map[string]*cloudmodel.AdditionalResource, error) { + var items []mysql.DomainAdditionalResource + mysql.Db.Select("domain", "content").Where("content!=''").Find(&items) + if len(items) == 0 { + mysql.Db.Select("domain", "compressed_content").Find(&items) + if len(items) == 0 { + return nil, gorm.ErrRecordNotFound + } + } + + domainToResource := make(map[string]*cloudmodel.AdditionalResource, len(items)) + for _, item := range items { + content := item.CompressedContent + if len(item.CompressedContent) == 0 { + content = []byte(item.Content) + } + additionalResource := &cloudmodel.AdditionalResource{} + if err := json.Unmarshal(content, &additionalResource); err != nil { + log.Errorf("domain (lcuuid: %s) json unmarshal content failed: %s", item.Domain, err.Error()) + continue + } + domainToResource[item.Domain] = additionalResource + } + + return domainToResource, nil +} + +func getVinterfaces(deviceUUID string, vifs []cloudmodel.VInterface, ips []cloudmodel.IP) []model.AdditionalResourceVInterface { + var resp []model.AdditionalResourceVInterface + for _, vif := range vifs { + if vif.DeviceLcuuid != deviceUUID { + continue + } + addVIF := model.AdditionalResourceVInterface{ + Mac: vif.Mac, + Name: vif.Name, + SubnetUUID: vif.NetworkLcuuid, + } + for _, ip := range ips { + if ip.VInterfaceLcuuid != vif.Lcuuid && + ip.Lcuuid != common.GenerateUUID(vif.Lcuuid+ip.IP) { + continue + } + addVIF.IPs = append(addVIF.IPs, ip.IP) + } + resp = append(resp, addVIF) + } + return resp +} diff --git a/server/controller/model/model.go b/server/controller/model/model.go index 7e70973fb86..5cf124d888b 100644 --- a/server/controller/model/model.go +++ b/server/controller/model/model.go @@ -367,11 +367,11 @@ type AdditionalResourceVPC struct { type AdditionalResourceSubnet struct { Name string `json:"name" yaml:"name" binding:"required"` - UUID string `json:"uuid" yaml:"uuid" binding:"required"` + UUID string `json:"uuid,omitempty" yaml:"uuid" binding:"required"` IsVIP bool `json:"is_vip" yaml:"is_vip"` Type int `json:"type" yaml:"type"` VPCUUID string `json:"vpc_uuid" yaml:"vpc_uuid" binding:"required"` - AZUUID string `json:"az_uuid" yaml:"az_uuid"` + AZUUID string `json:"az_uuid,omitempty" yaml:"az_uuid"` DomainUUID string `json:"domain_uuid" yaml:"domain_uuid" binding:"required"` CIDRs []string `json:"cidrs" yaml:"cidrs"` } @@ -399,7 +399,7 @@ type AdditionalResourceChost struct { type AdditionalResourceVInterface struct { SubnetUUID string `json:"subnet_uuid" yaml:"subnet_uuid"` - Name string `json:"name" yaml:"name"` + Name string `json:"name,omitempty" yaml:"name"` Mac string `json:"mac" yaml:"mac" binding:"required"` IPs []string `json:"ips" yaml:"ips"` }