Skip to content
This repository has been archived by the owner on Jan 21, 2020. It is now read-only.

[WIP] Initial OpenStack plugin #755

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/infrakit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
_ "github.com/docker/infrakit/pkg/run/v0/maas"
_ "github.com/docker/infrakit/pkg/run/v0/manager"
_ "github.com/docker/infrakit/pkg/run/v0/oneview"
_ "github.com/docker/infrakit/pkg/run/v0/openstack"
_ "github.com/docker/infrakit/pkg/run/v0/oracle"
_ "github.com/docker/infrakit/pkg/run/v0/packet"
_ "github.com/docker/infrakit/pkg/run/v0/rackhd"
Expand Down
191 changes: 191 additions & 0 deletions pkg/provider/openstack/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package openstack

import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"os"
"time"

logutil "github.com/docker/infrakit/pkg/log"
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
)

var log = logutil.New("module", "cli/x")

// Options capture the config parameters required to create the plugin
type Options struct {
OStackAuthURL string
OStackUserName string
OStackPassword string
OStackProject string
OStackUserDomain string
OStackCACert string
OStackInsecure bool
}

//miniFSM for managing the provisioning -> provisioned state
type provisioningFSM struct {
countdown int64 // ideally will be a counter of minutes / seconds
tags map[string]string // tags that will be passed back per a describe function
instanceName string // name that we will use as a lookup to the actual backend that is privisioning
}

// Spec is just whatever that can be unmarshalled into a generic JSON map
type Spec map[string]interface{}

// This contains the the details for the oneview instance
type plugin struct {
fsm []provisioningFSM
provider *gophercloud.ProviderClient
}

func init() {
rand.Seed(time.Now().UTC().UnixNano())
}

// NewOpenStackInstancePlugin will take the cmdline/env configuration
func NewOpenStackInstancePlugin(openStackOptions Options) instance.Plugin {

authOpts := gophercloud.AuthOptions{
DomainName: openStackOptions.OStackUserDomain,
IdentityEndpoint: openStackOptions.OStackAuthURL,
Username: openStackOptions.OStackUserName,
Password: openStackOptions.OStackPassword,
TenantName: openStackOptions.OStackProject,
}

provider, err := openstack.NewClient(authOpts.IdentityEndpoint)
if err != nil {
log.Crit("Failed to connect to OpenStack: %s", err)
os.Exit(-1)
}

provider.HTTPClient, err = openstackHTTPClient(openStackOptions.OStackCACert, openStackOptions.OStackInsecure)
if err != nil {
log.Crit("Failed to authenticate with certificate:", "error", err.Error)
os.Exit(-1)
}

err = openstack.Authenticate(provider, authOpts)
if err != nil {
log.Crit("Failed to authenticate with OpenStack:", "error", err)
os.Exit(-1)
}

// Exit with an error if we can't connect to OpenStack
if err != nil {
log.Crit("Error Logging into OpenStack")
os.Exit(-1)
}

log.Info("Succesfully logged in to OpenStack")

return &plugin{
provider: provider,
}
}

// Info returns a vendor specific name and version
func (p *plugin) VendorInfo() *spi.VendorInfo {
return &spi.VendorInfo{
InterfaceSpec: spi.InterfaceSpec{
Name: "infrakit-instance-openstack",
Version: "0.6.0",
},
URL: "https://github.com/docker/infrakit",
}
}

// ExampleProperties returns the properties / config of this plugin
func (p *plugin) ExampleProperties() *types.Any {
any, err := types.AnyValue(Spec{
"exampleString": "a_string",
"exampleBool": true,
"exampleInt": 1,
})
if err != nil {
return nil
}
return any
}

// Validate performs local validation on a provision request.
func (p *plugin) Validate(req *types.Any) error {
log.Debug("validate", req.String())

spec := Spec{}
if err := req.Decode(&spec); err != nil {
return err
}

log.Debug("Validated:", spec)
return nil
}

// Provision creates a new instance based on the spec.
func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) {

var properties map[string]interface{}

if spec.Properties != nil {
if err := spec.Properties.Decode(&properties); err != nil {
return nil, fmt.Errorf("Invalid instance properties: %s", err)
}
}

instanceName := instance.ID(fmt.Sprintf("InfraKit-%d", rand.Int63()))

return &instanceName, nil
}

// Label labels the instance
func (p *plugin) Label(instance instance.ID, labels map[string]string) error {
return fmt.Errorf("Openstack label updates are not implemented yet")
}

// Destroy terminates an existing instance.
func (p *plugin) Destroy(instance instance.ID, context instance.Context) error {
log.Info("Currently running %s on instance: %v", context, instance)
return nil
}

// DescribeInstances returns descriptions of all instances matching all of the provided tags.
// TODO - need to define the fitlering of tags => AND or OR of matches?
func (p *plugin) DescribeInstances(tags map[string]string, properties bool) ([]instance.Description, error) {
log.Debug("describe-instances", tags)
results := []instance.Description{}

return results, nil
}

func openstackHTTPClient(cacert string, insecure bool) (http.Client, error) {
if cacert == "" {
return http.Client{}, nil
}

caCertPool := x509.NewCertPool()
caCert, err := ioutil.ReadFile(cacert)
if err != nil {
return http.Client{}, errors.New("Can't read certificate file")
}
caCertPool.AppendCertsFromPEM(caCert)

tlsConfig := &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: insecure,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}

return http.Client{Transport: transport}, nil
}
110 changes: 110 additions & 0 deletions pkg/run/v0/openstack/openstack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package oneview

import (
"strings"

"github.com/docker/infrakit/pkg/launch/inproc"
logutil "github.com/docker/infrakit/pkg/log"
"github.com/docker/infrakit/pkg/plugin"
"github.com/docker/infrakit/pkg/provider/openstack"
"github.com/docker/infrakit/pkg/run"
"github.com/docker/infrakit/pkg/run/local"
"github.com/docker/infrakit/pkg/run/scope"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"
)

var log = logutil.New("module", "cli/x")

const (
// Kind is the canonical name of the plugin for starting up, etc.
Kind = "openstack"

// EnvNamespaceTags is the env to set for namespace tags. It's k=v,...
EnvNamespaceTags = "INFRAKIT_OPENSTACK_NAMESPACE_TAGS"

// EnvOStackURL is the env for setting the OpenStack auth URL to connect to
EnvOStackURL = "INFRAKIT_OPENSTACK_AUTHURL"

// EnvOStackUser is the Open Stack Username
EnvOStackUser = "INFRAKIT_OPENSTACK_USER"

// EnvOStackPass is the Open Stack Password
EnvOStackPass = "INFRAKIT_OPENSTACK_PASS"

// EnvOStackDomain is the Open Stack API Domain
EnvOStackDomain = "INFRAKIT_OPENSTACK_DOMAIN"

// EnvOStackProject is the Open Stack Project
EnvOStackProject = "INFRAKIT_OPENSTACK_PROJECT"
)

func init() {
inproc.Register(Kind, Run, DefaultOptions)
}

// Options capture the options for starting up the plugin.
type Options struct {
// Namespace is a set of kv pairs for tags that namespaces the resource instances
// TODO - this is currently implemented in AWS and other cloud providers but not
// in Open Stack
Namespace map[string]string

// OneViews is a list of OneView Servers - each corresponds to config of a plugin instance
OpenStacks []openstack.Options
}

func defaultNamespace() map[string]string {
t := map[string]string{}
list := local.Getenv(EnvNamespaceTags, "")
for _, v := range strings.Split(list, ",") {
p := strings.Split(v, "=")
if len(p) == 2 {
t[p[0]] = p[1]
}
}
return t
}

func defaultOSOptions() openstack.Options {

return openstack.Options{
OStackAuthURL: local.Getenv(EnvOStackURL, ""),
OStackUserName: local.Getenv(EnvOStackUser, ""),
OStackPassword: local.Getenv(EnvOStackPass, ""),
OStackProject: local.Getenv(EnvOStackProject, ""),
OStackUserDomain: local.Getenv(EnvOStackDomain, ""),
}
}

// DefaultOptions return an Options with default values filled in.
var DefaultOptions = Options{
Namespace: defaultNamespace(),
OpenStacks: []openstack.Options{
defaultOSOptions(),
},
}

// Run runs the plugin, blocking the current thread. Error is returned immediately
// if the plugin cannot be started.
func Run(scope scope.Scope, name plugin.Name,
config *types.Any) (transport plugin.Transport, impls map[run.PluginCode]interface{}, onStop func(), err error) {

options := DefaultOptions
err = config.Decode(&options)
if err != nil {
return
}

bareMetal := map[string]instance.Plugin{}

for _, os := range options.OpenStacks {
bareMetal[os.OStackAuthURL] = openstack.NewOpenStackInstancePlugin(os)
}

transport.Name = name
impls = map[run.PluginCode]interface{}{
run.Instance: bareMetal,
}
return
}
2 changes: 2 additions & 0 deletions vendor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,5 @@ github.com/micdoher/terraform-provider-ucs/ucsclient 50940f9
github.com/micdoher/terraform-provider-ucs/ucsclient/ucsinternal e82c113
github.com/micdoher/GoUtils 799bb49
gopkg.in/xmlpath.v2 860cbec
github.com/gophercloud/gophercloud 6e5b7d6
github.com/gophercloud/gophercloud/openstack 6e5b7d6
2 changes: 2 additions & 0 deletions vendor/github.com/gophercloud/gophercloud/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/gophercloud/gophercloud/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions vendor/github.com/gophercloud/gophercloud/.zuul.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
Loading