<commit_before>package apiserver_test

import (
	"errors"
	"fmt"
	"io"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/rpc"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api"
	"launchpad.net/juju-core/state/api/params"
	"launchpad.net/juju-core/state/apiserver"
	coretesting "launchpad.net/juju-core/testing"
	"net"
	stdtesting "testing"
	"time"
)

func TestAll(t *stdtesting.T) {
	coretesting.MgoTestPackage(t)
}

type suite struct {
	testing.JujuConnSuite
	listener net.Listener
}

var _ = Suite(&suite{})

func init() {
	apiserver.AuthenticationEnabled = true
}

func removeServiceAndUnits(c *C, service *state.Service) {
	// Destroy all units for the service.
	units, err := service.AllUnits()
	c.Assert(err, IsNil)
	for _, unit := range units {
		err = unit.EnsureDead()
		c.Assert(err, IsNil)
		err = unit.Remove()
		c.Assert(err, IsNil)
	}
	err = service.Refresh()
	c.Assert(err, IsNil)
	err = service.Destroy()
	c.Assert(err, IsNil)

	err = service.Refresh()
	c.Assert(state.IsNotFound(err), Equals, true)
}

var operationPermTests = []struct {
	about string
	// op performs the operation to be tested using the given state
	// connection.  It returns a function that should be used to
	// undo any changes made by the operation.
	op    func(c *C, st *api.State, mst *state.State) (reset func(), err error)
	allow []string
	deny  []string
}{{
	about: "Unit.Get",
	op:    opGetUnitWordpress0,
	deny:  []string{"user-admin", "user-other"},
}, {
	about: "Machine.Get",
	op:    opGetMachine1,
	deny:  []string{"user-admin", "user-other"},
}, {
	about: "Machine.SetPassword",
	op:    opMachine1SetPassword,
	allow: []string{"machine-0", "machine-1"},
}, {
	about: "Unit.SetPassword (on principal unit)",
	op:    opUnitSetPassword("<PASSWORD>/0"),
	allow: []string{"unit-wordpress-0", "machine-1"},
}, {
	about: "Unit.SetPassword (on subordinate unit)",
	op:    opUnitSetPassword("logging/0"),
	allow: []string{"unit-logging-0", "unit-wordpress-0"},
}, {
	about: "Client.Status",
	op:    opClientStatus,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceSet",
	op:    opClientServiceSet,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceSetYAML",
	op:    opClientServiceSetYAML,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceGet",
	op:    opClientServiceGet,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceExpose",
	op:    opClientServiceExpose,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceUnexpose",
	op:    opClientServiceUnexpose,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceDeploy",
	op:    opClientServiceDeploy,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.WatchAll",
	op:    opClientWatchAll,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.CharmInfo",
	op:    opClientCharmInfo,
	allow: []string{"user-admin", "user-other"},
},
}

// allowed returns the set of allowed entities given an allow list and a
// deny list.  If an allow list is specified, only those entities are
// allowed; otherwise those in deny are disallowed.
func allowed(all, allow, deny []string) map[string]bool {
	p := make(map[string]bool)
	if allow != nil {
		for _, e := range allow {
			p[e] = true
		}
		return p
	}
loop:
	for _, e0 := range all {
		for _, e1 := range deny {
			if e1 == e0 {
				continue loop
			}
		}
		p[e0] = true
	}
	return p
}

func (s *suite) TestOperationPerm(c *C) {
	entities := s.setUpScenario(c)
	for i, t := range operationPermTests {
		allow := allowed(entities, t.allow, t.deny)
		for _, e := range entities {
			c.Logf("test %d; %s; entity %q", i, t.about, e)
			st := s.openAs(c, e)
			reset, err := t.op(c, st, s.State)
			if allow[e] {
				c.Check(err, IsNil)
			} else {
				c.Check(err, ErrorMatches, "permission denied")
				c.Check(api.ErrCode(err), Equals, api.CodeUnauthorized)
			}
			reset()
			st.Close()
		}
	}
}

func opGetUnitWordpress0(c *C, st *api.State, mst *state.State) (func(), error) {
	u, err := st.Unit("wordpress/0")
	if err != nil {
		c.Check(u, IsNil)
	} else {
		name, ok := u.DeployerName()
		c.Check(ok, Equals, true)
		c.Check(name, Equals, "machine-1")
	}
	return func() {}, err
}

func opUnitSetPassword(unitName string) func(c *C, st *api.State, mst *state.State) (func(), error) {
	return func(c *C, st *api.State, mst *state.State) (func(), error) {
		u, err := st.Unit(unitName)
		if err != nil {
			c.Check(u, IsNil)
			return func() {}, err
		}
		err = u.SetPassword("<PASSWORD>")
		if err != nil {
			return func() {}, err
		}
		return func() {
			setDefaultPassword(c, u)
		}, nil
	}
}

func opGetMachine1(c *C, st *api.State, mst *state.State) (func(), error) {
	m, err := st.Machine("1")
	if err != nil {
		c.Check(m, IsNil)
	} else {
		name, ok := m.InstanceId()
		c.Assert(ok, Equals, true)
		c.Assert(name, Equals, "i-machine-1")
	}
	return func() {}, err
}

func opMachine1SetPassword(c *C, st *api.State, mst *state.State) (func(), error) {
	m, err := st.Machine("1")
	if err != nil {
		c.Check(m, IsNil)
		return func() {}, err
	}
	err = m.SetPassword("<PASSWORD>")
	if err != nil {
		return func() {}, err
	}
	return func() {
		setDefaultPassword(c, m)
	}, nil
}

func opClientCharmInfo(c *C, st *api.State, mst *state.State) (func(), error) {
	info, err := st.Client().CharmInfo("local:series/wordpress-3")
	if err != nil {
		c.Check(info, IsNil)
		return func() {}, err
	}
	c.Assert(err, IsNil)
	c.Assert(info.URL, Equals, "local:series/wordpress-3")
	c.Assert(info.Meta.Name, Equals, "wordpress")
	c.Assert(info.Revision, Equals, 3)
	return func() {}, nil
}

func opClientStatus(c *C, st *api.State, mst *state.State) (func(), error) {
	status, err := st.Client().Status()
	if err != nil {
		c.Check(status, IsNil)
		return func() {}, err
	}
	c.Assert(err, IsNil)
	c.Assert(status, DeepEquals, scenarioStatus)
	return func() {}, nil
}

func resetBlogTitle(c *C, st *api.State) func() {
	return func() {
		err := st.Client().ServiceSet("wordpress", map[string]string{
			"blog-title": "",
		})
		c.Assert(err, IsNil)
	}
}

func opClientServiceSet(c *C, st *api.State, mst *state.State) (func(), error) {
	err := st.Client().ServiceSet("wordpress", map[string]string{
		"blog-title": "foo",
	})
	if err != nil {
		return func() {}, err
	}
	return resetBlogTitle(c, st), nil
}

func opClientServiceSetYAML(c *C, st *api.State, mst *state.State) (func(), error) {
	err := st.Client().ServiceSetYAML("wordpress", `"blog-title": "foo"`)
	if err != nil {
		return func() {}, err
	}
	return resetBlogTitle(c, st), nil
}

func opClientServiceGet(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only shows that the call is made without error, ensuring the
	// signatures match.
	_, err := st.Client().ServiceGet("wordpress")
	if err != nil {
		return func() {}, err
	}
	c.Assert(err, IsNil)
	return func() {}, nil
}

func opClientServiceExpose(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only shows that the call is made without error, ensuring the
	// signatures match.
	err := st.Client().ServiceExpose("wordpress")
	if err != nil {
		return func() {}, err
	}
	c.Assert(err, IsNil)
	return func() {}, nil
}

func opClientServiceUnexpose(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only checks that the call is made without error, ensuring the
	// signatures match.
	err := st.Client().ServiceUnexpose("wordpress")
	if err != nil {
		return func() {}, err
	}
	c.Assert(err, IsNil)
	return func() {}, nil
}

func opClientServiceDeploy(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only checks that the call is made without error, ensuring the
	// signatures match.
	// We are cheating and using a local repo only.

	// Set the CharmStore to the test repository.
	serviceName := "mywordpress"
	charmUrl := "local:series/wordpress"
	parsedUrl := charm.MustParseURL(charmUrl)
	repo, err := charm.InferRepository(parsedUrl, coretesting.Charms.Path)
	originalServerCharmStore := apiserver.CharmStore
	apiserver.CharmStore = repo

	err = st.Client().ServiceDeploy(charmUrl, serviceName, 1, "")
	if err != nil {
		return func() {}, err
	}
	return func() {
		apiserver.CharmStore = originalServerCharmStore
		service, err := mst.Service(serviceName)
		c.Assert(err, IsNil)
		removeServiceAndUnits(c, service)
	}, nil
}

func opClientWatchAll(c *C, st *api.State) (func(), error) {
	watcher, err := st.Client().WatchAll()
	if err == nil {
		watcher.Stop()
	}
	return func() {}, err
}

// scenarioStatus describes the expected state
// of the juju environment set up by setUpScenario.
var scenarioStatus = &api.Status{
	Machines: map[string]api.MachineInfo{
		"0": {
			InstanceId: "i-machine-0",
		},
		"1": {
			InstanceId: "i-machine-1",
		},
		"2": {
			InstanceId: "i-machine-2",
		},
	},
}

// setUpScenario makes an environment scenario suitable for
// testing most kinds of access scenario. It returns
// a list of all the entities in the scenario.
//
// When the scenario is initialized, we have:
// user-admin
// user-other
// machine-0<commit_msg>Modify new test to have different signature<commit_after>package apiserver_test

import (
	"errors"
	"fmt"
	"io"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/rpc"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api"
	"launchpad.net/juju-core/state/api/params"
	"launchpad.net/juju-core/state/apiserver"
	coretesting "launchpad.net/juju-core/testing"
	"net"
	stdtesting "testing"
	"time"
)

func TestAll(t *stdtesting.T) {
	coretesting.MgoTestPackage(t)
}

type suite struct {
	testing.JujuConnSuite
	listener net.Listener
}

var _ = Suite(&suite{})

func init() {
	apiserver.AuthenticationEnabled = true
}

func removeServiceAndUnits(c *C, service *state.Service) {
	// Destroy all units for the service.
	units, err := service.AllUnits()
	c.Assert(err, IsNil)
	for _, unit := range units {
		err = unit.EnsureDead()
		c.Assert(err, IsNil)
		err = unit.Remove()
		c.Assert(err, IsNil)
	}
	err = service.Refresh()
	c.Assert(err, IsNil)
	err = service.Destroy()
	c.Assert(err, IsNil)

	err = service.Refresh()
	c.Assert(state.IsNotFound(err), Equals, true)
}

var operationPermTests = []struct {
	about string
	// op performs the operation to be tested using the given state
	// connection.  It returns a function that should be used to
	// undo any changes made by the operation.
	op    func(c *C, st *api.State, mst *state.State) (reset func(), err error)
	allow []string
	deny  []string
}{{
	about: "Unit.Get",
	op:    opGetUnitWordpress0,
	deny:  []string{"user-admin", "user-other"},
}, {
	about: "Machine.Get",
	op:    opGetMachine1,
	deny:  []string{"user-admin", "user-other"},
}, {
	about: "Machine.SetPassword",
	op:    opMachine1SetPassword,
	allow: []string{"machine-0", "machine-1"},
}, {
	about: "Unit.SetPassword (on principal unit)",
	op:    opUnitSetPassword("wordpress/0"),
	allow: []string{"unit-wordpress-0", "machine-1"},
}, {
	about: "Unit.SetPassword (on subordinate unit)",
	op:    opUnitSetPassword("logging/0"),
	allow: []string{"unit-logging-0", "unit-wordpress-0"},
}, {
	about: "Client.Status",
	op:    opClientStatus,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceSet",
	op:    opClientServiceSet,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceSetYAML",
	op:    opClientServiceSetYAML,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceGet",
	op:    opClientServiceGet,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceExpose",
	op:    opClientServiceExpose,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceUnexpose",
	op:    opClientServiceUnexpose,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.ServiceDeploy",
	op:    opClientServiceDeploy,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.WatchAll",
	op:    opClientWatchAll,
	allow: []string{"user-admin", "user-other"},
}, {
	about: "Client.CharmInfo",
	op:    opClientCharmInfo,
	allow: []string{"user-admin", "user-other"},
},
}

// allowed returns the set of allowed entities given an allow list and a
// deny list.  If an allow list is specified, only those entities are
// allowed; otherwise those in deny are disallowed.
func allowed(all, allow, deny []string) map[string]bool {
	p := make(map[string]bool)
	if allow != nil {
		for _, e := range allow {
			p[e] = true
		}
		return p
	}
loop:
	for _, e0 := range all {
		for _, e1 := range deny {
			if e1 == e0 {
				continue loop
			}
		}
		p[e0] = true
	}
	return p
}

func (s *suite) TestOperationPerm(c *C) {
	entities := s.setUpScenario(c)
	for i, t := range operationPermTests {
		allow := allowed(entities, t.allow, t.deny)
		for _, e := range entities {
			c.Logf("test %d; %s; entity %q", i, t.about, e)
			st := s.openAs(c, e)
			reset, err := t.op(c, st, s.State)
			if allow[e] {
				c.Check(err, IsNil)
			} else {
				c.Check(err, ErrorMatches, "permission denied")
				c.Check(api.ErrCode(err), Equals, api.CodeUnauthorized)
			}
			reset()
			st.Close()
		}
	}
}

func opGetUnitWordpress0(c *C, st *api.State, mst *state.State) (func(), error) {
	u, err := st.Unit("wordpress/0")
	if err != nil {
		c.Check(u, IsNil)
	} else {
		name, ok := u.DeployerName()
		c.Check(ok, Equals, true)
		c.Check(name, Equals, "machine-1")
	}
	return func() {}, err
}

func opUnitSetPassword(unitName string) func(c *C, st *api.State, mst *state.State) (func(), error) {
	return func(c *C, st *api.State, mst *state.State) (func(), error) {
		u, err := st.Unit(unitName)
		if err != nil {
			c.Check(u, IsNil)
			return func() {}, err
		}
		err = u.SetPassword("<PASSWORD>")
		if err != nil {
			return func() {}, err
		}
		return func() {
			setDefaultPassword(c, u)
		}, nil
	}
}

func opGetMachine1(c *C, st *api.State, mst *state.State) (func(), error) {
	m, err := st.Machine("1")
	if err != nil {
		c.Check(m, IsNil)
	} else {
		name, ok := m.InstanceId()
		c.Assert(ok, Equals, true)
		c.Assert(name, Equals, "i-machine-1")
	}
	return func() {}, err
}

func opMachine1SetPassword(c *C, st *api.State, mst *state.State) (func(), error) {
	m, err := st.Machine("1")
	if err != nil {
		c.Check(m, IsNil)
		return func() {}, err
	}
	err = m.SetPassword("<PASSWORD>")
	if err != nil {
		return func() {}, err
	}
	return func() {
		setDefaultPassword(c, m)
	}, nil
}

func opClientCharmInfo(c *C, st *api.State, mst *state.State) (func(), error) {
	info, err := st.Client().CharmInfo("local:series/wordpress-3")
	if err != nil {
		c.Check(info, IsNil)
		return func() {}, err
	}
	c.Assert(err, IsNil)
	c.Assert(info.URL, Equals, "local:series/wordpress-3")
	c.Assert(info.Meta.Name, Equals, "wordpress")
	c.Assert(info.Revision, Equals, 3)
	return func() {}, nil
}

func opClientStatus(c *C, st *api.State, mst *state.State) (func(), error) {
	status, err := st.Client().Status()
	if err != nil {
		c.Check(status, IsNil)
		return func() {}, err
	}
	c.Assert(err, IsNil)
	c.Assert(status, DeepEquals, scenarioStatus)
	return func() {}, nil
}

func resetBlogTitle(c *C, st *api.State) func() {
	return func() {
		err := st.Client().ServiceSet("wordpress", map[string]string{
			"blog-title": "",
		})
		c.Assert(err, IsNil)
	}
}

func opClientServiceSet(c *C, st *api.State, mst *state.State) (func(), error) {
	err := st.Client().ServiceSet("wordpress", map[string]string{
		"blog-title": "foo",
	})
	if err != nil {
		return func() {}, err
	}
	return resetBlogTitle(c, st), nil
}

func opClientServiceSetYAML(c *C, st *api.State, mst *state.State) (func(), error) {
	err := st.Client().ServiceSetYAML("wordpress", `"blog-title": "foo"`)
	if err != nil {
		return func() {}, err
	}
	return resetBlogTitle(c, st), nil
}

func opClientServiceGet(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only shows that the call is made without error, ensuring the
	// signatures match.
	_, err := st.Client().ServiceGet("wordpress")
	if err != nil {
		return func() {}, err
	}
	c.Assert(err, IsNil)
	return func() {}, nil
}

func opClientServiceExpose(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only shows that the call is made without error, ensuring the
	// signatures match.
	err := st.Client().ServiceExpose("wordpress")
	if err != nil {
		return func() {}, err
	}
	c.Assert(err, IsNil)
	return func() {}, nil
}

func opClientServiceUnexpose(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only checks that the call is made without error, ensuring the
	// signatures match.
	err := st.Client().ServiceUnexpose("wordpress")
	if err != nil {
		return func() {}, err
	}
	c.Assert(err, IsNil)
	return func() {}, nil
}

func opClientServiceDeploy(c *C, st *api.State, mst *state.State) (func(), error) {
	// This test only checks that the call is made without error, ensuring the
	// signatures match.
	// We are cheating and using a local repo only.

	// Set the CharmStore to the test repository.
	serviceName := "mywordpress"
	charmUrl := "local:series/wordpress"
	parsedUrl := charm.MustParseURL(charmUrl)
	repo, err := charm.InferRepository(parsedUrl, coretesting.Charms.Path)
	originalServerCharmStore := apiserver.CharmStore
	apiserver.CharmStore = repo

	err = st.Client().ServiceDeploy(charmUrl, serviceName, 1, "")
	if err != nil {
		return func() {}, err
	}
	return func() {
		apiserver.CharmStore = originalServerCharmStore
		service, err := mst.Service(serviceName)
		c.Assert(err, IsNil)
		removeServiceAndUnits(c, service)
	}, nil
}

func opClientWatchAll(c *C, st *api.State, mst *state.State) (func(), error) {
	watcher, err := st.Client().WatchAll()
	if err == nil {
		watcher.Stop()
	}
	return func() {}, err
}

// scenarioStatus describes the expected state
// of the juju environment set up by setUpScenario.
var scenarioStatus = &api.Status{
	Machines: map[string]api.MachineInfo{
		"0": {
			InstanceId: "i-machine-0",
		},
		"1": {
			InstanceId: "i-machine-1",
		},
		"2": {
			InstanceId: "i-machine-2",
		},
	},
}

// setUpScenario makes an environment scenario suitable for
// testing most kinds of access scenario. It returns
// a list of all the entities in the scenario.
//
// When the scenario is initialized, we have:
// user-admin
// user-other
// machine-0
{'fraction_non_alphanumeric': 0.17075282784001733, 'fraction_numerical': 0.0037884938031065647, 'mean_word_length': 3.885774722369117, 'pattern_counts': {'":': 12, '<': 8, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2105623', 'n_tokens_mistral': 7113, 'n_tokens_neox': 6787, 'n_words': 1753}