-
Notifications
You must be signed in to change notification settings - Fork 262
Maas instance plugin #411
Maas instance plugin #411
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Infrakit MaaS Instance Plugin | ||
An example of [Infrakit](https://github.com/docker/infrakit) Instance Pluging for [Canonical MaaS](https://maas.io/) node. | ||
Purpose of this plugin is to support Bare metal deploy with Infrakit. | ||
## Go get | ||
`$go get github.com/YujiOshima/infrakit-maas-plugin` | ||
|
||
## Get start | ||
Requires: | ||
* Needs virtualbox % sudo apt-get install virtualbox | ||
* Needs vagrant 1.6.x | ||
* Needs ansible installed on host machine | ||
* MaaS Server | ||
* API Key ( See https://maas.ubuntu.com/docs/maascli.html ) | ||
* MaaS Nodes (You need all node be `Ready` state. Register and commision manually.) | ||
* Spec file ( example: maas-vanilla.yml ) | ||
|
||
As this is only demo, we use virtual box. we should be use BareMetal Server Cluster with MaaS | ||
|
||
### Set up MaaS Cluster | ||
|
||
``` | ||
$ git clone https://github.com/YujiOshima/vagrant-maas-in-a-box | ||
$ cd vagrant-maas-in-a-box | ||
$ vagrant up maas | ||
``` | ||
|
||
In your browser visit http://localhost:8080/MAAS | ||
|
||
Default login and password: `admin` `pass` | ||
|
||
Import boot images in `Images` tab. | ||
|
||
! Wait for import images. It takes some time. ! | ||
|
||
Set up nodes. | ||
|
||
``` | ||
./setup-nodes.sh | ||
``` | ||
|
||
3 nodes are enlisted in MaaS. | ||
|
||
check `Nodes` tab. | ||
|
||
Get MaaS API KEY. with web browser, or run `APIKEY=vagrant ssh maas -c "sudo maas-region-admin apikey --username admin"` | ||
|
||
### Run Infrakit group, vanilla flavor plugin. | ||
|
||
``` | ||
$ build/infrakit-group-default | ||
``` | ||
|
||
``` | ||
$ build/infrakit-flavor-vanilla | ||
``` | ||
|
||
Run MaaS instance plugin. | ||
|
||
``` | ||
$ $GOPATH/bin/infrakit-maas-plugin --apikey $APIKEY --url http://localhost:8080/MAAS | ||
INFO[0000] Listening at: /home/ubuntu/.infrakit/plugins/instance-maas | ||
``` | ||
Group commit! | ||
|
||
``` | ||
$ build/infrakit group commit $GOPATH/src/github.com/YujiOshima/infrakit-maas-plugin/maas-vanilla.yml | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"time" | ||
|
||
"github.com/docker/infrakit/pkg/spi/instance" | ||
"github.com/docker/infrakit/pkg/types" | ||
maas "github.com/juju/gomaasapi" | ||
"net/url" | ||
) | ||
|
||
// NewMaasPlugin creates an instance plugin for MaaS. | ||
func NewMaasPlugin(dir string, key string, url string, version string) instance.Plugin { | ||
authClient, err := maas.NewAuthenticatedClient(url, key, version) | ||
if err != nil { | ||
return nil | ||
} | ||
maasobj := maas.NewMAAS(*authClient) | ||
return &maasPlugin{MaasfilesDir: dir, MaasObj: maasobj} | ||
} | ||
|
||
type maasPlugin struct { | ||
MaasfilesDir string | ||
MaasObj *maas.MAASObject | ||
} | ||
|
||
// Validate performs local validation on a provision request. | ||
func (m maasPlugin) Validate(req *types.Any) error { | ||
return nil | ||
} | ||
|
||
func (m maasPlugin) convertSpecToMaasParam(spec map[string]interface{}) url.Values { | ||
param := url.Values{} | ||
return param | ||
} | ||
|
||
func (m maasPlugin) checkDuplicate(hostname string) (bool, error) { | ||
files, err := ioutil.ReadDir(m.MaasfilesDir) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
for _, file := range files { | ||
if !file.IsDir() { | ||
continue | ||
} | ||
machineDir := path.Join(m.MaasfilesDir, file.Name()) | ||
hnData, err := ioutil.ReadFile(path.Join(machineDir, "MachineName")) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
return false, err | ||
} | ||
if hostname == string(hnData) { | ||
return true, nil | ||
} | ||
} | ||
return false, nil | ||
} | ||
|
||
// Provision creates a new instance. | ||
func (m maasPlugin) 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) | ||
} | ||
} | ||
nodeListing := m.MaasObj.GetSubObject("nodes") | ||
jsonResponse, err := nodeListing.CallPost("acquire", m.convertSpecToMaasParam(properties)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
acquiredNode, err := jsonResponse.GetMAASObject() | ||
if err != nil { | ||
return nil, err | ||
} | ||
listNodeObjects, err := nodeListing.CallGet("list", url.Values{}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
listNodes, err := listNodeObjects.GetArray() | ||
if err != nil { | ||
return nil, err | ||
} | ||
notDuplicate := false | ||
for range listNodes { | ||
hostname, err := acquiredNode.GetField("hostname") | ||
if err != nil { | ||
return nil, err | ||
} | ||
isdup, err := m.checkDuplicate(hostname) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if isdup { | ||
jsonResponse, err = nodeListing.CallPost("acquire", m.convertSpecToMaasParam(properties)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
acquiredNode, err = jsonResponse.GetMAASObject() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} else { | ||
notDuplicate = true | ||
break | ||
} | ||
} | ||
if !notDuplicate { | ||
return nil, errors.New("Failed to aquire node") | ||
} | ||
isAcquired := make(chan bool) | ||
go func() { | ||
for state, err := acquiredNode.GetField("substatus_name"); state != "Allocated" && err == nil; state, err = acquiredNode.GetField("substatus_name") { | ||
time.Sleep(100) | ||
} | ||
isAcquired <- true | ||
return | ||
}() | ||
<-isAcquired | ||
params := url.Values{} | ||
if _, err = acquiredNode.CallPost("start", params); err != nil { | ||
return nil, err | ||
} | ||
hostname, err := acquiredNode.GetField("hostname") | ||
if err != nil { | ||
return nil, err | ||
} | ||
id := instance.ID(hostname) | ||
machineDir, err := ioutil.TempDir(m.MaasfilesDir, "infrakit-") | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := ioutil.WriteFile(path.Join(machineDir, "MachineName"), []byte(hostname), 0755); err != nil { | ||
return nil, err | ||
} | ||
tagData, err := types.AnyValue(spec.Tags) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := ioutil.WriteFile(path.Join(machineDir, "tags"), tagData.Bytes(), 0666); err != nil { | ||
return nil, err | ||
} | ||
if spec.LogicalID != nil { | ||
if err := ioutil.WriteFile(path.Join(machineDir, "ip"), []byte(*spec.LogicalID), 0666); err != nil { | ||
return nil, err | ||
} | ||
} | ||
return &id, nil | ||
} | ||
|
||
// Label labels the instance | ||
func (m maasPlugin) Label(instance instance.ID, labels map[string]string) error { | ||
machineDir := path.Join(m.MaasfilesDir, string(instance)) | ||
tagFile := path.Join(machineDir, "tags") | ||
buff, err := ioutil.ReadFile(tagFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
tags := map[string]string{} | ||
err = types.AnyBytes(buff).Decode(&tags) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for k, v := range labels { | ||
tags[k] = v | ||
} | ||
|
||
encoded, err := types.AnyValue(tags) | ||
if err != nil { | ||
return err | ||
} | ||
return ioutil.WriteFile(tagFile, encoded.Bytes(), 0666) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Files are written locally to store tags... is there a way in MAAS to tag resources and query by tags? https://docs.ubuntu.com/maas/2.1/en/intro-concepts#tags If we can reduce amount of state we need store locally, the better. If there are ways to do tagging via the MAAS api, then we should consider making that change in a future PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that is a better implementation. This time we implemented it in a simple way that does not depend on the api version, but we will change the information to keep it in a robust way without duplication. |
||
} | ||
|
||
// Destroy terminates an existing instance. | ||
func (m maasPlugin) Destroy(id instance.ID) error { | ||
fmt.Println("Destroying ", id) | ||
nodeListing := m.MaasObj.GetSubObject("nodes") | ||
listNodeObjects, err := nodeListing.CallGet("list", url.Values{}) | ||
if err != nil { | ||
return err | ||
} | ||
listNodes, err := listNodeObjects.GetArray() | ||
for _, nodeObj := range listNodes { | ||
node, err := nodeObj.GetMAASObject() | ||
hostname, err := node.GetField("hostname") | ||
if hostname == string(id) { | ||
if state, _ := node.GetField("substatus_name"); state == "Deploying" { | ||
params := url.Values{} | ||
if _, err = node.CallPost("abort_operation", params); err != nil { | ||
return err | ||
} | ||
} | ||
params := url.Values{} | ||
if _, err = node.CallPost("release", params); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
files, err := ioutil.ReadDir(m.MaasfilesDir) | ||
if err != nil { | ||
return err | ||
} | ||
for _, file := range files { | ||
if !file.IsDir() { | ||
continue | ||
} | ||
machineDir := path.Join(m.MaasfilesDir, file.Name()) | ||
hostname, err := ioutil.ReadFile(path.Join(machineDir, "MachineName")) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
return err | ||
} else if id == instance.ID(hostname) { | ||
if err := os.RemoveAll(machineDir); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// DescribeInstances returns descriptions of all instances matching all of the provided tags. | ||
func (m maasPlugin) DescribeInstances(tags map[string]string) ([]instance.Description, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment above -- is there a way in MAAS to query by tags of the nodes? Using files here is ok but we'd have to make sure these files are highly available. |
||
files, err := ioutil.ReadDir(m.MaasfilesDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
descriptions := []instance.Description{} | ||
for _, file := range files { | ||
if !file.IsDir() { | ||
continue | ||
} | ||
machineDir := path.Join(m.MaasfilesDir, file.Name()) | ||
tagData, err := ioutil.ReadFile(path.Join(machineDir, "tags")) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
return nil, err | ||
} | ||
machineTags := map[string]string{} | ||
if err := types.AnyBytes(tagData).Decode(&machineTags); err != nil { | ||
return nil, err | ||
} | ||
allMatched := true | ||
for k, v := range tags { | ||
value, exists := machineTags[k] | ||
if !exists || v != value { | ||
allMatched = false | ||
break | ||
} | ||
} | ||
if allMatched { | ||
hostname, err := ioutil.ReadFile(path.Join(machineDir, "MachineName")) | ||
if err == nil { | ||
} else { | ||
if !os.IsNotExist(err) { | ||
return nil, err | ||
} | ||
} | ||
descriptions = append(descriptions, instance.Description{ | ||
ID: instance.ID(hostname), | ||
Tags: machineTags, | ||
}) | ||
} | ||
} | ||
return descriptions, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh..This is a mistake. Fixed. |
||
"ID": "cattle", | ||
"Properties": { | ||
"Allocation": { | ||
"Size": 2 | ||
}, | ||
"Instance": { | ||
"Plugin": "instance-maas", | ||
"Properties": { | ||
} | ||
}, | ||
"Flavor": { | ||
"Plugin": "flavor-vanilla", | ||
"Properties": { | ||
"Init": [ | ||
], | ||
"Tags": { | ||
"tier": "web", | ||
"project": "infrakit" | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file included in this PR? I don't see it. Would you mind including it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I update the Readme. Now I have only a simple document, but I will enhance it with the addition of functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I was confused. This is a file from your repo... I see now, so there's no need to include this.