github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/openstack/resource_openstack_networking_port_v2.go (about)

     1  package openstack
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/hashcode"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  
    13  	"github.com/gophercloud/gophercloud"
    14  	"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
    15  )
    16  
    17  func resourceNetworkingPortV2() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceNetworkingPortV2Create,
    20  		Read:   resourceNetworkingPortV2Read,
    21  		Update: resourceNetworkingPortV2Update,
    22  		Delete: resourceNetworkingPortV2Delete,
    23  		Importer: &schema.ResourceImporter{
    24  			State: schema.ImportStatePassthrough,
    25  		},
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"region": &schema.Schema{
    29  				Type:        schema.TypeString,
    30  				Required:    true,
    31  				ForceNew:    true,
    32  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
    33  			},
    34  			"name": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Optional: true,
    37  				ForceNew: false,
    38  			},
    39  			"network_id": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Required: true,
    42  				ForceNew: true,
    43  			},
    44  			"admin_state_up": &schema.Schema{
    45  				Type:     schema.TypeBool,
    46  				Optional: true,
    47  				ForceNew: false,
    48  				Computed: true,
    49  			},
    50  			"mac_address": &schema.Schema{
    51  				Type:     schema.TypeString,
    52  				Optional: true,
    53  				ForceNew: true,
    54  				Computed: true,
    55  			},
    56  			"tenant_id": &schema.Schema{
    57  				Type:     schema.TypeString,
    58  				Optional: true,
    59  				ForceNew: true,
    60  				Computed: true,
    61  			},
    62  			"device_owner": &schema.Schema{
    63  				Type:     schema.TypeString,
    64  				Optional: true,
    65  				ForceNew: true,
    66  				Computed: true,
    67  			},
    68  			"security_group_ids": &schema.Schema{
    69  				Type:     schema.TypeSet,
    70  				Optional: true,
    71  				ForceNew: false,
    72  				Computed: true,
    73  				Elem:     &schema.Schema{Type: schema.TypeString},
    74  				Set:      schema.HashString,
    75  			},
    76  			"device_id": &schema.Schema{
    77  				Type:     schema.TypeString,
    78  				Optional: true,
    79  				ForceNew: true,
    80  				Computed: true,
    81  			},
    82  			"fixed_ip": &schema.Schema{
    83  				Type:     schema.TypeList,
    84  				Optional: true,
    85  				ForceNew: false,
    86  				Computed: true,
    87  				Elem: &schema.Resource{
    88  					Schema: map[string]*schema.Schema{
    89  						"subnet_id": &schema.Schema{
    90  							Type:     schema.TypeString,
    91  							Required: true,
    92  						},
    93  						"ip_address": &schema.Schema{
    94  							Type:     schema.TypeString,
    95  							Optional: true,
    96  							Computed: true,
    97  						},
    98  					},
    99  				},
   100  			},
   101  			"allowed_address_pairs": &schema.Schema{
   102  				Type:     schema.TypeSet,
   103  				Optional: true,
   104  				ForceNew: false,
   105  				Computed: true,
   106  				Set:      allowedAddressPairsHash,
   107  				Elem: &schema.Resource{
   108  					Schema: map[string]*schema.Schema{
   109  						"ip_address": &schema.Schema{
   110  							Type:     schema.TypeString,
   111  							Required: true,
   112  						},
   113  						"mac_address": &schema.Schema{
   114  							Type:     schema.TypeString,
   115  							Optional: true,
   116  							Computed: true,
   117  						},
   118  					},
   119  				},
   120  			},
   121  			"value_specs": &schema.Schema{
   122  				Type:     schema.TypeMap,
   123  				Optional: true,
   124  				ForceNew: true,
   125  			},
   126  		},
   127  	}
   128  }
   129  
   130  func resourceNetworkingPortV2Create(d *schema.ResourceData, meta interface{}) error {
   131  	config := meta.(*Config)
   132  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   133  	if err != nil {
   134  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   135  	}
   136  
   137  	createOpts := PortCreateOpts{
   138  		ports.CreateOpts{
   139  			Name:                d.Get("name").(string),
   140  			AdminStateUp:        resourcePortAdminStateUpV2(d),
   141  			NetworkID:           d.Get("network_id").(string),
   142  			MACAddress:          d.Get("mac_address").(string),
   143  			TenantID:            d.Get("tenant_id").(string),
   144  			DeviceOwner:         d.Get("device_owner").(string),
   145  			SecurityGroups:      resourcePortSecurityGroupsV2(d),
   146  			DeviceID:            d.Get("device_id").(string),
   147  			FixedIPs:            resourcePortFixedIpsV2(d),
   148  			AllowedAddressPairs: resourceAllowedAddressPairsV2(d),
   149  		},
   150  		MapValueSpecs(d),
   151  	}
   152  
   153  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   154  	p, err := ports.Create(networkingClient, createOpts).Extract()
   155  	if err != nil {
   156  		return fmt.Errorf("Error creating OpenStack Neutron network: %s", err)
   157  	}
   158  	log.Printf("[INFO] Network ID: %s", p.ID)
   159  
   160  	log.Printf("[DEBUG] Waiting for OpenStack Neutron Port (%s) to become available.", p.ID)
   161  
   162  	stateConf := &resource.StateChangeConf{
   163  		Target:     []string{"ACTIVE"},
   164  		Refresh:    waitForNetworkPortActive(networkingClient, p.ID),
   165  		Timeout:    2 * time.Minute,
   166  		Delay:      5 * time.Second,
   167  		MinTimeout: 3 * time.Second,
   168  	}
   169  
   170  	_, err = stateConf.WaitForState()
   171  
   172  	d.SetId(p.ID)
   173  
   174  	return resourceNetworkingPortV2Read(d, meta)
   175  }
   176  
   177  func resourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) error {
   178  	config := meta.(*Config)
   179  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   180  	if err != nil {
   181  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   182  	}
   183  
   184  	p, err := ports.Get(networkingClient, d.Id()).Extract()
   185  	if err != nil {
   186  		return CheckDeleted(d, err, "port")
   187  	}
   188  
   189  	log.Printf("[DEBUG] Retrieved Port %s: %+v", d.Id(), p)
   190  
   191  	d.Set("name", p.Name)
   192  	d.Set("admin_state_up", p.AdminStateUp)
   193  	d.Set("network_id", p.NetworkID)
   194  	d.Set("mac_address", p.MACAddress)
   195  	d.Set("tenant_id", p.TenantID)
   196  	d.Set("device_owner", p.DeviceOwner)
   197  	d.Set("security_group_ids", p.SecurityGroups)
   198  	d.Set("device_id", p.DeviceID)
   199  
   200  	// Convert FixedIPs to list of map
   201  	var ips []map[string]interface{}
   202  	for _, ipObject := range p.FixedIPs {
   203  		ip := make(map[string]interface{})
   204  		ip["subnet_id"] = ipObject.SubnetID
   205  		ip["ip_address"] = ipObject.IPAddress
   206  		ips = append(ips, ip)
   207  	}
   208  	d.Set("fixed_ip", ips)
   209  
   210  	// Convert AllowedAddressPairs to list of map
   211  	var pairs []map[string]interface{}
   212  	for _, pairObject := range p.AllowedAddressPairs {
   213  		pair := make(map[string]interface{})
   214  		pair["ip_address"] = pairObject.IPAddress
   215  		pair["mac_address"] = pairObject.MACAddress
   216  		pairs = append(pairs, pair)
   217  	}
   218  	d.Set("allowed_address_pairs", pairs)
   219  
   220  	d.Set("region", GetRegion(d))
   221  
   222  	return nil
   223  }
   224  
   225  func resourceNetworkingPortV2Update(d *schema.ResourceData, meta interface{}) error {
   226  	config := meta.(*Config)
   227  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   228  	if err != nil {
   229  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   230  	}
   231  
   232  	var updateOpts ports.UpdateOpts
   233  
   234  	if d.HasChange("name") {
   235  		updateOpts.Name = d.Get("name").(string)
   236  	}
   237  
   238  	if d.HasChange("admin_state_up") {
   239  		updateOpts.AdminStateUp = resourcePortAdminStateUpV2(d)
   240  	}
   241  
   242  	if d.HasChange("device_owner") {
   243  		updateOpts.DeviceOwner = d.Get("device_owner").(string)
   244  	}
   245  
   246  	if d.HasChange("security_group_ids") {
   247  		updateOpts.SecurityGroups = resourcePortSecurityGroupsV2(d)
   248  	}
   249  
   250  	if d.HasChange("device_id") {
   251  		updateOpts.DeviceID = d.Get("device_id").(string)
   252  	}
   253  
   254  	if d.HasChange("fixed_ip") {
   255  		updateOpts.FixedIPs = resourcePortFixedIpsV2(d)
   256  	}
   257  
   258  	if d.HasChange("allowed_address_pairs") {
   259  		updateOpts.AllowedAddressPairs = resourceAllowedAddressPairsV2(d)
   260  	}
   261  
   262  	log.Printf("[DEBUG] Updating Port %s with options: %+v", d.Id(), updateOpts)
   263  
   264  	_, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract()
   265  	if err != nil {
   266  		return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err)
   267  	}
   268  
   269  	return resourceNetworkingPortV2Read(d, meta)
   270  }
   271  
   272  func resourceNetworkingPortV2Delete(d *schema.ResourceData, meta interface{}) error {
   273  	config := meta.(*Config)
   274  	networkingClient, err := config.networkingV2Client(GetRegion(d))
   275  	if err != nil {
   276  		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
   277  	}
   278  
   279  	stateConf := &resource.StateChangeConf{
   280  		Pending:    []string{"ACTIVE"},
   281  		Target:     []string{"DELETED"},
   282  		Refresh:    waitForNetworkPortDelete(networkingClient, d.Id()),
   283  		Timeout:    2 * time.Minute,
   284  		Delay:      5 * time.Second,
   285  		MinTimeout: 3 * time.Second,
   286  	}
   287  
   288  	_, err = stateConf.WaitForState()
   289  	if err != nil {
   290  		return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err)
   291  	}
   292  
   293  	d.SetId("")
   294  	return nil
   295  }
   296  
   297  func resourcePortSecurityGroupsV2(d *schema.ResourceData) []string {
   298  	rawSecurityGroups := d.Get("security_group_ids").(*schema.Set)
   299  	groups := make([]string, rawSecurityGroups.Len())
   300  	for i, raw := range rawSecurityGroups.List() {
   301  		groups[i] = raw.(string)
   302  	}
   303  	return groups
   304  }
   305  
   306  func resourcePortFixedIpsV2(d *schema.ResourceData) interface{} {
   307  	rawIP := d.Get("fixed_ip").([]interface{})
   308  
   309  	if len(rawIP) == 0 {
   310  		return nil
   311  	}
   312  
   313  	ip := make([]ports.IP, len(rawIP))
   314  	for i, raw := range rawIP {
   315  		rawMap := raw.(map[string]interface{})
   316  		ip[i] = ports.IP{
   317  			SubnetID:  rawMap["subnet_id"].(string),
   318  			IPAddress: rawMap["ip_address"].(string),
   319  		}
   320  	}
   321  	return ip
   322  }
   323  
   324  func resourceAllowedAddressPairsV2(d *schema.ResourceData) []ports.AddressPair {
   325  	// ports.AddressPair
   326  	rawPairs := d.Get("allowed_address_pairs").(*schema.Set).List()
   327  
   328  	if len(rawPairs) == 0 {
   329  		return nil
   330  	}
   331  
   332  	pairs := make([]ports.AddressPair, len(rawPairs))
   333  	for i, raw := range rawPairs {
   334  		rawMap := raw.(map[string]interface{})
   335  		pairs[i] = ports.AddressPair{
   336  			IPAddress:  rawMap["ip_address"].(string),
   337  			MACAddress: rawMap["mac_address"].(string),
   338  		}
   339  	}
   340  	return pairs
   341  }
   342  
   343  func resourcePortAdminStateUpV2(d *schema.ResourceData) *bool {
   344  	value := false
   345  
   346  	if raw, ok := d.GetOk("admin_state_up"); ok && raw == true {
   347  		value = true
   348  	}
   349  
   350  	return &value
   351  }
   352  
   353  func allowedAddressPairsHash(v interface{}) int {
   354  	var buf bytes.Buffer
   355  	m := v.(map[string]interface{})
   356  	buf.WriteString(fmt.Sprintf("%s", m["ip_address"].(string)))
   357  
   358  	return hashcode.String(buf.String())
   359  }
   360  
   361  func waitForNetworkPortActive(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc {
   362  	return func() (interface{}, string, error) {
   363  		p, err := ports.Get(networkingClient, portId).Extract()
   364  		if err != nil {
   365  			return nil, "", err
   366  		}
   367  
   368  		log.Printf("[DEBUG] OpenStack Neutron Port: %+v", p)
   369  		if p.Status == "DOWN" || p.Status == "ACTIVE" {
   370  			return p, "ACTIVE", nil
   371  		}
   372  
   373  		return p, p.Status, nil
   374  	}
   375  }
   376  
   377  func waitForNetworkPortDelete(networkingClient *gophercloud.ServiceClient, portId string) resource.StateRefreshFunc {
   378  	return func() (interface{}, string, error) {
   379  		log.Printf("[DEBUG] Attempting to delete OpenStack Neutron Port %s", portId)
   380  
   381  		p, err := ports.Get(networkingClient, portId).Extract()
   382  		if err != nil {
   383  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   384  				log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId)
   385  				return p, "DELETED", nil
   386  			}
   387  			return p, "ACTIVE", err
   388  		}
   389  
   390  		err = ports.Delete(networkingClient, portId).ExtractErr()
   391  		if err != nil {
   392  			if _, ok := err.(gophercloud.ErrDefault404); ok {
   393  				log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId)
   394  				return p, "DELETED", nil
   395  			}
   396  			return p, "ACTIVE", err
   397  		}
   398  
   399  		log.Printf("[DEBUG] OpenStack Port %s still active.\n", portId)
   400  		return p, "ACTIVE", nil
   401  	}
   402  }