yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/securitygroup.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package qcloud
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    27  	"yunion.io/x/pkg/util/secrules"
    28  	"yunion.io/x/pkg/utils"
    29  
    30  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    31  	"yunion.io/x/cloudmux/pkg/multicloud"
    32  )
    33  
    34  type SecurityGroupPolicy struct {
    35  	region            *SRegion
    36  	PolicyIndex       int                          // 安全组规则索引号。
    37  	Protocol          string                       // 协议, 取值: TCP,UDP, ICMP。
    38  	Port              string                       // 端口(all, 离散port, range)。
    39  	ServiceTemplate   ServiceTemplateSpecification // 协议端口ID或者协议端口组ID。ServiceTemplate和Protocol+Port互斥。
    40  	CidrBlock         string                       // 网段或IP(互斥)。
    41  	SecurityGroupId   string                       // 已绑定安全组的网段或IP。
    42  	AddressTemplate   AddressTemplateSpecification // IP地址ID或者ID地址组ID。
    43  	Action            string                       // ACCEPT 或 DROP。
    44  	PolicyDescription string                       // 安全组规则描述。
    45  	direction         string
    46  }
    47  
    48  type ServiceTemplateSpecification struct {
    49  	ServiceId      string //	协议端口ID,例如:ppm-f5n1f8da。
    50  	ServiceGroupId string //	协议端口组ID,例如:ppmg-f5n1f8da。
    51  }
    52  
    53  type AddressTemplateSpecification struct {
    54  	AddressId      string //	IP地址ID,例如:ipm-2uw6ujo6。
    55  	AddressGroupId string //	IP地址组ID,例如:ipmg-2uw6ujo6。
    56  }
    57  
    58  type SecurityGroupPolicySet struct {
    59  	Version string
    60  	Egress  []SecurityGroupPolicy //	出站规则。
    61  	Ingress []SecurityGroupPolicy //	入站规则。
    62  }
    63  
    64  type SSecurityGroup struct {
    65  	multicloud.SSecurityGroup
    66  	QcloudTags
    67  	region                 *SRegion
    68  	SecurityGroupId        string    //		安全组实例ID,例如:sg-ohuuioma。
    69  	SecurityGroupName      string    //		安全组名称,可任意命名,但不得超过60个字符。
    70  	SecurityGroupDesc      string    //		安全组备注,最多100个字符。
    71  	ProjectId              string    //		项目id,默认0。可在qcloud控制台项目管理页面查询到。
    72  	IsDefault              bool      // 	是否是默认安全组,默认安全组不支持删除。
    73  	CreatedTime            time.Time // 	安全组创建时间。
    74  	SecurityGroupPolicySet SecurityGroupPolicySet
    75  }
    76  
    77  func (self *SRegion) GetSecurityGroups(ids []string, vpcId string, name string, offset int, limit int) ([]SSecurityGroup, int, error) {
    78  	if limit > 50 || limit <= 0 {
    79  		limit = 50
    80  	}
    81  	params := make(map[string]string)
    82  	params["Limit"] = fmt.Sprintf("%d", limit)
    83  	params["Offset"] = fmt.Sprintf("%d", offset)
    84  
    85  	if len(name) > 0 {
    86  		params["Filters.0.Name"] = "security-group-name"
    87  		params["Filters.0.Values.0"] = name
    88  	}
    89  
    90  	for idx, id := range ids {
    91  		params[fmt.Sprintf("SecurityGroupIds.%d", idx)] = id
    92  	}
    93  
    94  	resp, err := self.vpcRequest("DescribeSecurityGroups", params)
    95  	if err != nil {
    96  		return nil, 0, errors.Wrapf(err, "DescribeSecurityGroups")
    97  	}
    98  
    99  	secgrps := make([]SSecurityGroup, 0)
   100  	err = resp.Unmarshal(&secgrps, "SecurityGroupSet")
   101  	if err != nil {
   102  		return nil, 0, errors.Wrapf(err, "resp.Unmarshal")
   103  	}
   104  	total, _ := resp.Float("TotalCount")
   105  	return secgrps, int(total), nil
   106  }
   107  
   108  func (self *SSecurityGroup) GetVpcId() string {
   109  	//腾讯云安全组未与vpc关联,统一使用normal
   110  	return "normal"
   111  }
   112  
   113  func (self *SSecurityGroup) GetId() string {
   114  	return self.SecurityGroupId
   115  }
   116  
   117  func (self *SSecurityGroup) GetGlobalId() string {
   118  	return self.SecurityGroupId
   119  }
   120  
   121  func (self *SSecurityGroup) GetDescription() string {
   122  	return self.SecurityGroupDesc
   123  }
   124  
   125  func (self *SSecurityGroup) GetName() string {
   126  	if len(self.SecurityGroupName) > 0 {
   127  		return self.SecurityGroupName
   128  	}
   129  	return self.SecurityGroupId
   130  }
   131  
   132  func (self *SecurityGroupPolicy) String() string {
   133  	rules := self.toRules()
   134  	result := []string{}
   135  	for _, rule := range rules {
   136  		result = append(result, rule.String())
   137  	}
   138  	return strings.Join(result, ";")
   139  }
   140  
   141  type ReferredSecurityGroup struct {
   142  	SecurityGroupId          string
   143  	ReferredSecurityGroupIds []string
   144  }
   145  
   146  func (self *SSecurityGroup) GetReferences() ([]cloudprovider.SecurityGroupReference, error) {
   147  	references, err := self.region.DescribeSecurityGroupReferences(self.SecurityGroupId)
   148  	if err != nil {
   149  		return nil, errors.Wrapf(err, "DescribeSecurityGroupReferences")
   150  	}
   151  	ret := []cloudprovider.SecurityGroupReference{}
   152  	for _, refer := range references {
   153  		if refer.SecurityGroupId == self.SecurityGroupId {
   154  			for _, id := range refer.ReferredSecurityGroupIds {
   155  				ret = append(ret, cloudprovider.SecurityGroupReference{
   156  					Id: id,
   157  				})
   158  			}
   159  		}
   160  	}
   161  	return ret, nil
   162  }
   163  
   164  func (self *SRegion) DescribeSecurityGroupReferences(id string) ([]ReferredSecurityGroup, error) {
   165  	params := map[string]string{
   166  		"Region":             self.Region,
   167  		"SecurityGroupIds.0": id,
   168  	}
   169  	resp, err := self.vpcRequest("DescribeSecurityGroupReferences", params)
   170  	if err != nil {
   171  		return nil, errors.Wrapf(err, "DescribeSecurityGroupReferences")
   172  	}
   173  	ret := []ReferredSecurityGroup{}
   174  	err = resp.Unmarshal(&ret, "ReferredSecurityGroupSet")
   175  	if err != nil {
   176  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   177  	}
   178  	return ret, nil
   179  }
   180  
   181  func (self *SecurityGroupPolicy) toRules() []cloudprovider.SecurityRule {
   182  	result := []cloudprovider.SecurityRule{}
   183  	rule := cloudprovider.SecurityRule{
   184  		ExternalId: fmt.Sprintf("%d", self.PolicyIndex),
   185  		SecurityRule: secrules.SecurityRule{
   186  			Action:    secrules.SecurityRuleAllow,
   187  			Protocol:  secrules.PROTO_ANY,
   188  			Direction: secrules.TSecurityRuleDirection(self.direction),
   189  			Priority:  self.PolicyIndex,
   190  			Ports:     []int{},
   191  			PortStart: -1,
   192  			PortEnd:   -1,
   193  		},
   194  	}
   195  	if len(self.SecurityGroupId) != 0 {
   196  		rule.ParseCIDR("0.0.0.0/0")
   197  		rule.PeerSecgroupId = self.SecurityGroupId
   198  	}
   199  	if strings.ToLower(self.Action) == "drop" {
   200  		rule.Action = secrules.SecurityRuleDeny
   201  	}
   202  	if utils.IsInStringArray(strings.ToLower(self.Protocol), []string{"tcp", "udp", "icmp"}) {
   203  		rule.Protocol = strings.ToLower(self.Protocol)
   204  	}
   205  	if strings.Index(self.Port, ",") > 0 {
   206  		for _, _port := range strings.Split(self.Port, ",") {
   207  			port, err := strconv.Atoi(_port)
   208  			if err != nil {
   209  				log.Errorf("parse secgroup port %s %s error %v", self.Port, _port, err)
   210  				continue
   211  			}
   212  			rule.Ports = append(rule.Ports, port)
   213  		}
   214  	} else if strings.Index(self.Port, "-") > 0 {
   215  		ports := strings.Split(self.Port, "-")
   216  		if len(ports) == 2 {
   217  			portStart, err := strconv.Atoi(ports[0])
   218  			if err != nil {
   219  				return nil
   220  			}
   221  			portEnd, err := strconv.Atoi(ports[1])
   222  			if err != nil {
   223  				return nil
   224  			}
   225  			rule.PortStart, rule.PortEnd = portStart, portEnd
   226  		}
   227  	} else if strings.ToLower(self.Port) != "all" {
   228  		port, err := strconv.Atoi(self.Port)
   229  		if err != nil {
   230  			return nil
   231  		}
   232  		rule.PortStart, rule.PortEnd = port, port
   233  	}
   234  
   235  	if len(self.AddressTemplate.AddressGroupId) > 0 {
   236  		addressGroup, total, err := self.region.AddressGroupList(self.AddressTemplate.AddressGroupId, "", 0, 1)
   237  		if err != nil {
   238  			log.Errorf("Get AddressList %s failed %v", self.AddressTemplate.AddressId, err)
   239  			return nil
   240  		}
   241  		if total != 1 {
   242  			return nil
   243  		}
   244  		for i := 0; i < len(addressGroup[0].AddressTemplateIdSet); i++ {
   245  			rules, err := self.getAddressRules(rule, addressGroup[0].AddressTemplateIdSet[i])
   246  			if err != nil {
   247  				return nil
   248  			}
   249  			result = append(result, rules...)
   250  		}
   251  	} else if len(self.AddressTemplate.AddressId) > 0 {
   252  		rules, err := self.getAddressRules(rule, self.AddressTemplate.AddressId)
   253  		if err != nil {
   254  			return nil
   255  		}
   256  		result = append(result, rules...)
   257  	} else if len(self.SecurityGroupId) > 0 {
   258  		rule.PeerSecgroupId = self.SecurityGroupId
   259  		result = append(result, rule)
   260  	} else if len(self.CidrBlock) > 0 {
   261  		rule.ParseCIDR(self.CidrBlock)
   262  		result = append(result, rule)
   263  	}
   264  	return result
   265  }
   266  
   267  func (self *SecurityGroupPolicy) getAddressRules(rule cloudprovider.SecurityRule, addressId string) ([]cloudprovider.SecurityRule, error) {
   268  	result := []cloudprovider.SecurityRule{}
   269  	address, total, err := self.region.AddressList(addressId, "", 0, 1)
   270  	if err != nil {
   271  		log.Errorf("Get AddressList %s failed %v", self.AddressTemplate.AddressId, err)
   272  		return nil, err
   273  	}
   274  	if total != 1 {
   275  		return nil, fmt.Errorf("failed to find address %s", addressId)
   276  	}
   277  	for _, ip := range address[0].AddressSet {
   278  		rule.ParseCIDR(ip)
   279  		result = append(result, rule)
   280  	}
   281  	return result, nil
   282  }
   283  
   284  func (self *SSecurityGroup) GetRules() ([]cloudprovider.SecurityRule, error) {
   285  	policySet, err := self.region.DescribeSecurityGroupPolicies(self.SecurityGroupId)
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  	for i := 0; i < len(policySet.Egress); i++ {
   290  		policySet.Egress[i].direction = "out"
   291  	}
   292  	for i := 0; i < len(policySet.Ingress); i++ {
   293  		policySet.Ingress[i].direction = "in"
   294  	}
   295  	originRules := []SecurityGroupPolicy{}
   296  	originRules = append(originRules, policySet.Egress...)
   297  	originRules = append(originRules, policySet.Ingress...)
   298  	for i := 0; i < len(originRules); i++ {
   299  		originRules[i].region = self.region
   300  	}
   301  	rules := []cloudprovider.SecurityRule{}
   302  	for _, rule := range originRules {
   303  		subRules := rule.toRules()
   304  		rules = append(rules, subRules...)
   305  	}
   306  	return rules, nil
   307  }
   308  
   309  func (self *SSecurityGroup) GetStatus() string {
   310  	return ""
   311  }
   312  
   313  func (self *SSecurityGroup) IsEmulated() bool {
   314  	return false
   315  }
   316  
   317  func (self *SSecurityGroup) Refresh() error {
   318  	groups, total, err := self.region.GetSecurityGroups([]string{self.SecurityGroupId}, "", "", 0, 0)
   319  	if err != nil {
   320  		return err
   321  	}
   322  	if total < 1 {
   323  		return cloudprovider.ErrNotFound
   324  	}
   325  	return jsonutils.Update(self, groups[0])
   326  }
   327  
   328  func (self *SSecurityGroup) SyncRules(common, inAdds, outAdds, inDels, outDels []cloudprovider.SecurityRule) error {
   329  	rules := append(common, append(inAdds, outAdds...)...)
   330  	sort.Sort(cloudprovider.SecurityRuleSet(rules))
   331  	return self.region.syncSecgroupRules(self.SecurityGroupId, rules)
   332  }
   333  
   334  func (self *SRegion) syncSecgroupRules(secgroupId string, rules []cloudprovider.SecurityRule) error {
   335  	params := map[string]string{
   336  		"SecurityGroupId": secgroupId,
   337  		"SortPolicys":     "True",
   338  	}
   339  	egressIndex, ingressIndex := 0, 0
   340  	for _, rule := range rules {
   341  		switch rule.Direction {
   342  		case secrules.DIR_IN:
   343  			params = convertSecgroupRule(ingressIndex, rule, params)
   344  			ingressIndex++
   345  		case secrules.DIR_OUT:
   346  			params = convertSecgroupRule(egressIndex, rule, params)
   347  			egressIndex++
   348  		}
   349  	}
   350  	_, err := self.vpcRequest("ModifySecurityGroupPolicies", params)
   351  	if err != nil {
   352  		return errors.Wrapf(err, "ModifySecurityGroupPolicies")
   353  	}
   354  	if egressIndex == 0 || ingressIndex == 0 {
   355  		ruleSet, err := self.DescribeSecurityGroupPolicies(secgroupId)
   356  		if err != nil {
   357  			return errors.Wrapf(err, "DescribeSecurityGroupPolicies")
   358  		}
   359  		params = map[string]string{
   360  			"SecurityGroupId": secgroupId,
   361  		}
   362  		if egressIndex == 0 && len(ruleSet.Egress) > 0 {
   363  			for idx, rule := range ruleSet.Egress {
   364  				params[fmt.Sprintf("SecurityGroupPolicySet.Egress.%d.PolicyIndex", idx)] = fmt.Sprintf("%d", rule.PolicyIndex)
   365  			}
   366  		}
   367  		if ingressIndex == 0 && len(ruleSet.Ingress) > 0 {
   368  			for idx, rule := range ruleSet.Ingress {
   369  				params[fmt.Sprintf("SecurityGroupPolicySet.Ingress.%d.PolicyIndex", idx)] = fmt.Sprintf("%d", rule.PolicyIndex)
   370  			}
   371  		}
   372  		if len(params) > 1 {
   373  			_, err = self.vpcRequest("DeleteSecurityGroupPolicies", params)
   374  			if err != nil {
   375  				return errors.Wrapf(err, "DeleteSecurityGroupPolicies")
   376  			}
   377  		}
   378  	}
   379  	return nil
   380  }
   381  
   382  func convertSecgroupRule(policyIndex int, rule cloudprovider.SecurityRule, params map[string]string) map[string]string {
   383  	direction := "Egress"
   384  	action := "accept"
   385  	if rule.Action == secrules.SecurityRuleDeny {
   386  		action = "drop"
   387  	}
   388  	protocol := "ALL"
   389  	if rule.Protocol != secrules.PROTO_ANY {
   390  		protocol = rule.Protocol
   391  	}
   392  	if rule.Direction == secrules.DIR_IN {
   393  		direction = "Ingress"
   394  	}
   395  	params[fmt.Sprintf("SecurityGroupPolicySet.%s.%d.Action", direction, policyIndex)] = action
   396  	params[fmt.Sprintf("SecurityGroupPolicySet.%s.%d.PolicyDescription", direction, policyIndex)] = rule.Description
   397  	params[fmt.Sprintf("SecurityGroupPolicySet.%s.%d.Protocol", direction, policyIndex)] = protocol
   398  	if len(rule.PeerSecgroupId) > 0 {
   399  		params[fmt.Sprintf("SecurityGroupPolicySet.%s.%d.SecurityGroupId", direction, policyIndex)] = rule.PeerSecgroupId
   400  	} else {
   401  		params[fmt.Sprintf("SecurityGroupPolicySet.%s.%d.CidrBlock", direction, policyIndex)] = rule.IPNet.String()
   402  	}
   403  	if rule.Protocol == secrules.PROTO_TCP || rule.Protocol == secrules.PROTO_UDP {
   404  		port := "ALL"
   405  		if rule.PortEnd > 0 && rule.PortStart > 0 {
   406  			if rule.PortStart == rule.PortEnd {
   407  				port = fmt.Sprintf("%d", rule.PortStart)
   408  			} else {
   409  				port = fmt.Sprintf("%d-%d", rule.PortStart, rule.PortEnd)
   410  			}
   411  		} else if len(rule.Ports) > 0 {
   412  			ports := []string{}
   413  			for _, _port := range rule.Ports {
   414  				ports = append(ports, fmt.Sprintf("%d", _port))
   415  			}
   416  			port = strings.Join(ports, ",")
   417  		}
   418  		params[fmt.Sprintf("SecurityGroupPolicySet.%s.%d.Port", direction, policyIndex)] = port
   419  	}
   420  	return params
   421  }
   422  
   423  func (self *SRegion) DescribeSecurityGroupPolicies(secGroupId string) (*SecurityGroupPolicySet, error) {
   424  	params := make(map[string]string)
   425  	params["Region"] = self.Region
   426  	params["SecurityGroupId"] = secGroupId
   427  
   428  	body, err := self.vpcRequest("DescribeSecurityGroupPolicies", params)
   429  	if err != nil {
   430  		log.Errorf("DescribeSecurityGroupAttribute fail %s", err)
   431  		return nil, err
   432  	}
   433  
   434  	policies := SecurityGroupPolicySet{}
   435  	err = body.Unmarshal(&policies, "SecurityGroupPolicySet")
   436  	if err != nil {
   437  		return nil, errors.Wrapf(err, "body.Unmarshal")
   438  	}
   439  	return &policies, nil
   440  }
   441  
   442  func (self *SRegion) DeleteSecurityGroup(secGroupId string) error {
   443  	params := make(map[string]string)
   444  	params["Region"] = self.Region
   445  	params["SecurityGroupId"] = secGroupId
   446  	_, err := self.vpcRequest("DeleteSecurityGroup", params)
   447  	return err
   448  }
   449  
   450  type AddressTemplate struct {
   451  	AddressSet          []string
   452  	AddressTemplateId   string
   453  	AddressTemplateName string
   454  	CreatedTime         time.Time
   455  }
   456  
   457  func (self *SRegion) AddressList(addressId, addressName string, offset, limit int) ([]AddressTemplate, int, error) {
   458  	params := map[string]string{}
   459  	filter := 0
   460  	if len(addressId) > 0 {
   461  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "address-template-id"
   462  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = addressId
   463  		filter++
   464  	}
   465  	if len(addressName) > 0 {
   466  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "address-template-name"
   467  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = addressName
   468  		filter++
   469  	}
   470  	params["Offset"] = fmt.Sprintf("%d", offset)
   471  	if limit == 0 {
   472  		limit = 20
   473  	}
   474  	params["Limit"] = fmt.Sprintf("%d", limit)
   475  	body, err := self.vpcRequest("DescribeAddressTemplates", params)
   476  	if err != nil {
   477  		return nil, 0, err
   478  	}
   479  	addressTemplates := []AddressTemplate{}
   480  	err = body.Unmarshal(&addressTemplates, "AddressTemplateSet")
   481  	if err != nil {
   482  		return nil, 0, err
   483  	}
   484  	total, _ := body.Float("TotalCount")
   485  	return addressTemplates, int(total), nil
   486  }
   487  
   488  type AddressTemplateGroup struct {
   489  	AddressTemplateIdSet     []string
   490  	AddressTemplateGroupName string
   491  	AddressTemplateGroupId   string
   492  	CreatedTime              time.Time
   493  }
   494  
   495  func (self *SRegion) AddressGroupList(groupId, groupName string, offset, limit int) ([]AddressTemplateGroup, int, error) {
   496  	params := map[string]string{}
   497  	filter := 0
   498  	if len(groupId) > 0 {
   499  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "address-template-group-id"
   500  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = groupId
   501  		filter++
   502  	}
   503  	if len(groupName) > 0 {
   504  		params[fmt.Sprintf("Filters.%d.Name", filter)] = "address-template-group-name"
   505  		params[fmt.Sprintf("Filters.%d.Values.0", filter)] = groupName
   506  		filter++
   507  	}
   508  	params["Offset"] = fmt.Sprintf("%d", offset)
   509  	if limit == 0 {
   510  		limit = 20
   511  	}
   512  	params["Limit"] = fmt.Sprintf("%d", limit)
   513  	body, err := self.vpcRequest("DescribeAddressTemplateGroups", params)
   514  	if err != nil {
   515  		return nil, 0, err
   516  	}
   517  	addressTemplateGroups := []AddressTemplateGroup{}
   518  	err = body.Unmarshal(&addressTemplateGroups, "AddressTemplateGroupSet")
   519  	if err != nil {
   520  		return nil, 0, err
   521  	}
   522  	total, _ := body.Float("TotalCount")
   523  	return addressTemplateGroups, int(total), nil
   524  }
   525  
   526  func (self *SRegion) DeleteSecgroupRule(secId string, direction string, index int) error {
   527  	params := map[string]string{
   528  		"SecurityGroupId": secId,
   529  	}
   530  	switch direction {
   531  	case secrules.DIR_IN:
   532  		params["SecurityGroupPolicySet.Ingress.0.PolicyIndex"] = fmt.Sprintf("%d", index)
   533  	case secrules.DIR_OUT:
   534  		params["SecurityGroupPolicySet.Egress.0.PolicyIndex"] = fmt.Sprintf("%d", index)
   535  	}
   536  	_, err := self.vpcRequest("DeleteSecurityGroupPolicies", params)
   537  	return errors.Wrapf(err, "DeleteSecurityGroupPolicies")
   538  }
   539  
   540  func (self *SRegion) CreateSecurityGroup(name, projectId, description string) (*SSecurityGroup, error) {
   541  	params := make(map[string]string)
   542  	params["Region"] = self.Region
   543  	params["GroupName"] = name
   544  	params["GroupDescription"] = description
   545  	if len(projectId) > 0 {
   546  		params["ProjectId"] = projectId
   547  	}
   548  
   549  	if len(description) == 0 {
   550  		params["GroupDescription"] = "Customize Create"
   551  	}
   552  	secgroup := SSecurityGroup{region: self}
   553  	body, err := self.vpcRequest("CreateSecurityGroup", params)
   554  	if err != nil {
   555  		return nil, errors.Wrap(err, "CreateSecurityGroup")
   556  	}
   557  	err = body.Unmarshal(&secgroup, "SecurityGroup")
   558  	if err != nil {
   559  		return nil, errors.Wrap(err, "body.Unmarshal")
   560  	}
   561  	return &secgroup, nil
   562  }
   563  
   564  func (self *SSecurityGroup) GetProjectId() string {
   565  	return self.ProjectId
   566  }
   567  
   568  func (self *SSecurityGroup) Delete() error {
   569  	return self.region.DeleteSecurityGroup(self.SecurityGroupId)
   570  }